This is for the latest 2019 PLANEA test!

Easy ggplot theme.

Let’s define some function to interact easily with the database.

#' @title plan_connect
#'
#' @description This function takes the Postgres environment variables
#' to automatically connect to the planeadb database.
#' The connection must be saved into a variable in order to
#' use future functions. I'm cheating and not reading from .env.
#'
#' @examples con <- prev_connect()
#'
#' @param No parameter is needed.
#' @export
plan_connect <- function(){
  DBI::dbConnect(RPostgreSQL::PostgreSQL(),
  host     =  "localhost",
  user     =  "planea",
  password =  "planea",
  port     =  5432,
  dbname   =  "planea")
}

#' @title load_query
#'
#' @description Allows to run specific queries with the dbrsocial syntax.
#'
#' @param connection DBI connection. A connection to a database
#' @param schema variable. A valid schema from a database on the
#' @param the_table.  An existing table in the given schema.
#' @param colums string. The columns in the database we want to retrieve
#' information
#' @param options string. Part of the SQL query with containing WHERE, ORDER,
#' LIMIT and so statements
#'
#' @examples geom_muni <-
#' load_query(con,raw,sifode,columns="entidadfederativa",options="WHERE
#' mes='Abril'")
#' @export
load_query <- function(connection,schema,the_table,columns="*",options=""){
    the_query <- "SELECT %s FROM %s.%s"
    complete <- paste0(the_query," ",options)
    schema    <- deparse(substitute(schema))
    the_table <- deparse(substitute(the_table))
    initial <- RPostgreSQL::dbSendQuery(connection,
                             sprintf(complete,columns,schema,the_table))
    return(initial)
}

#' @title load_table
#'
#' @description This function loads a connection to
#' a given table from a particular schema in a database
#' connection.
#'
#' @param connection DBI connection. A connection to a database
#' must be open and given.
#' @param schema variable. A valid schema from a database on the
#' connected database.
#' @param the_table. An existing table in the given schema.
#'
#' @examples the_dic<-load_table(con,raw,sifode_dic)
#' @export
load_table <- function(connection,schema,the_table){
    the_query <- "SELECT * FROM %s.%s"
    schema    <- deparse(substitute(schema))
    the_table <- deparse(substitute(the_table))
    initial <- RPostgreSQL::dbSendQuery(connection,
                             sprintf(the_query,schema,the_table))
}

#' @title large_table
#'
#' @description This function loads a connection to a large table without
#' loading it to memory.
#'
#' @param connection DBI connection. A connection to a database
#' must be open and given.
#' @param schema variable. A valid schema from a database on the
#' connected database.
#' @param the_table. An existing table in the given schema.
#'
#' @examples cuis_table <- large_table(con,raw,cuis_39_9)
#' @export
large_table <- function(connection,schema,the_table){
    schema    <- deparse(substitute(schema))
    the_table <- deparse(substitute(the_table))
    retrieved <- dplyr::tbl(connection,dbplyr::in_schema(schema,the_table))
    return(retrieved)
}

#' @title clear_results
#'
#' @description This function clears the results from a previous executed
#' query.
#'
#' @param connection DBI connection. A connection to a database must be open and given.
#'
#' @examples clear_results(con)
#' @export
clear_results <- function(connection){
    DBI::dbClearResult(DBI::dbListResults(connection)[[1]])
}

#' @title join_tables
#'
#' @description Returns a match between two tbl by defined key
#' @param left_table a tbl-like object
#' @param right_table a tbl-like object
#' @param left_key the column name from left_table to compare
#' @param right_key the column name from right_table to compare
#'
#' @examples join_tables(cuis_sample,llave_hogar_h,domicilios_sample_query,llave_hogar_h)
#' @export
join_tables <- function(left_table, left_key, right_table, right_key){
    left_key <- substitute(left_key)
    right_key <- deparse(substitute(right_key))
    in_tables <- left_table %>%
        dplyr::filter(left_key %in% right_table[[right_key]])
    return(in_tables)
}

#' @title retrieve_result
#'
#' @description Return the fetch results of a query
#' @param query An exec unfetched query
#'
#' @examples sample_table(load_table(prev_connect(),raw,sifode))
#' @export
retrieve_result <- function(query,n=-1,number=Inf){
    if (class(query)[1] == "tbl_dbi"){
        the_table <- dplyr::collect(query,n=number)
        return(the_table)
    }
    else{
    the_table <- DBI::dbFetch(query,n)
    return(the_table)
    }
}

#' @title load_geom
#'
#' @description Gives a "ready to go" data frame for geometry plotting
#'
#' @param connection DBI connection. A connection to a database
#' @param schema variable. A valid schema from a database on the
#' @param the_table.  An existing table in the given schema.
#' @param colums string. The columns in the database we want to retrieve.
#' Defaults cve_ent, cve_mun, cve_muni.
#' @param geom_col . The name of the column in the database that contains a geometry
#' @param col_shape. The name of the column that we want to use to join
#' information
#' @param options string. Part of the SQL query with containing WHERE, ORDER,
#' LIMIT and so statements
#'
#' @examples geom_muni <- load_geom(con1,raw,geom_municipios,geom_col=geom,col_shape=cve_muni,options=options)
#' @export
load_geom <- function(connection,schema,the_table,columns="\"CVEGEO\", \"CVE_ENT\", \"CVE_MUN\"", geom_col, col_shape, options=""){
    geom_col <- deparse(substitute(geom_col))
    schema    <- deparse(substitute(schema))
    the_table <- deparse(substitute(the_table))
    col_shape <- deparse(substitute(col_shape))
    geom_col2 <- paste0("\"",geom_col,"\"")

    the_query <- "SELECT %s FROM %s.%s"
    geom_col_as <- sprintf(", %s as geom",geom_col2)
    columns <- paste0(columns,geom_col_as)
    complete <- paste0(the_query," ",options)

    initial <- RPostgreSQL::dbSendQuery(connection,
                             sprintf(complete,columns,schema,the_table)) %>%
    retrieve_result()

    mun_shp = rangeMapper::WKT2SpatialPolygonsDataFrame(initial, geom = "geom", id = col_shape)
    mun_df <- fortify(mun_shp, region = col_shape)
    names(mun_df)[names(mun_df)=="id"] <- col_shape

    return(mun_df)
}

#' @title discon_db
#'
#' @description This function disconnects a PostgreSQL
#' connection.
#'
#' @param connection DBI connection. A connection to a database must be open and given.
#'
#' @examples discon_db(con)
#' @export
discon_db <- function(connection){
    RPostgreSQL::dbDisconnect(connection)
}

First we load the PLANEA results.

# discon_db(con)
con <- plan_connect()

query <- load_table(con,public,planea)
planea <- query %>% retrieve_result()
clear_results(con)
[1] TRUE

We load the geometries but will use them later

geom_muni <- load_geom(con,public,geom_muni,geom_col=WKT,col_shape=CVEGEO)
the_names <- colnames(planea)
logros <- the_names[19:length((the_names))]
planea[logros] <- sapply(planea[logros],as.numeric)
NAs introduced by coercionNAs introduced by coercionNAs introduced by coercionNAs introduced by coercionNAs introduced by coercionNAs introduced by coercionNAs introduced by coercionNAs introduced by coercion
planea$municipio <- planea$municipio %>% iconv(from="UTF-8",to="ASCII//TRANSLIT") %>% toupper()

We need to change some municipalities names in order to join this database with the CONAPO one. This are the missed-matched names in the tables of the database. I’ll change the ones from the schools.

from_schools <- c("CD. DEL CARMEN","DOCTOR BELISARIO DOMINGUEZ","TEMOSACHI","PEJPYON BLANCO","DOLORES HIDALGO","SILAO","ZAPOTLAN EL GRANDE (CIUDAD GUZMAN)","CUAUTITLAN (DE GARCIA BARRAGAN)","SAN MARTIN DE BOLA&OS","CA&ADAS DE OBREGON","SAN JOSE? DEL RINCON","VALLE DE CHALCO SOLIDARIDA","SAN MARTIN DE LAS PIRAMIDE","HEROICA ZITACUARO","TANHUATO DE GUERRERO","ARIO DE ROSALES","TUMBISCATIO DE RUIZ","JUNGAPEO DE JUAREZ","ACUITZIO DEL CANJE","SUSUPUATO DE GUERRERO","GABRIEL ZAMORA (LOMBARDIA)","BENITO JUAREZ (LAURELES)","HUETAMO DE NUNEZ","TIQUICHEO","TLALPUJAHUA DE RAYON","PARACHO DE VERDUZCO","DR. BESLISARIO DOMINGUEZ","TLALTIZAPAN","EL NAYAR","SAN MATEO YUCUTINDO","EL ROSARIO","ATLTZAYANCA","MEDELLIN DE BRAVO","NANCHITAL DE LAZARO CARDENAS DEL RI")

to_conapo <- c("CARMEN","DR. BELISARIO DOMINGUEZ","TEMOSACHIC","PENON BLANCO","DOLORES HIDALGO CUNA DE LA INDEPENDENCIA NACIONAL","SILAO DE LA VICTORIA","ZAPOTLAN EL GRANDE","CUAUTITLAN DE GARCIA BARRAGAN","SAN MARTIN DE BOLANOS","CANADAS DE OBREGON","SAN JOSE DEL RINCON","VALLE DE CHALCO SOLIDARIDAD","SAN MARTIN DE LAS PIRAMIDES","ZITACUARO","TANHUATO","ARIO","TUMBISCATIO","JUNGAPEO","ACUITZIO","SUSUPUATO","GABRIEL ZAMORA","JUAREZ","HUETAMO","TIQUICHEO DE NICOLAS ROMERO","TLALPUJAHUA","PARACHO","DR. BELISARIO DOMINGUEZ","TLALTIZAPAN DE ZAPATA","DEL NAYAR","SAN MATEO YUCUTINDOO","ROSARIO","ALTZAYANCA","MEDELLIN","NANCHITAL DE LAZARO CARDENAS DEL RIO")

planea$municipio <- plyr::mapvalues(planea$municipio, to=to_conapo, from=from_schools)
The following `from` values were not present in `x`: DR. BESLISARIO DOMINGUEZ

Load the CONAPO table

the_options <- "WHERE \"ANO\" = 2015"
query <- load_query(con,public,conapo,options=the_options)
conapo <- query %>% retrieve_result()
clear_results(con)
[1] TRUE
conapo$CLAVE <- conapo$CLAVE %>% str_pad(width=5,pad="0")
conapo$NOM_ENT %<>% str_replace_all("Oaxaca.*","Oaxaca")
conapo$MUN <- conapo$MUN %>% iconv(from="UTF-8",to="ASCII//TRANSLIT") %>% toupper() %>% str_replace_all("SAN PEDRO MIXTEPEC.*","SAN PEDRO MIXTEPEC")
conapo %<>% select(CLAVE,NOM_ENT,MUN,SEXO,POB) %<>% dplyr::group_by(CLAVE,NOM_ENT,MUN) %>% dplyr::summarise(POP=sum(POB))

Load the marginalization table and change the variable names to usable ones.

the_options <- "WHERE \"YEAR\" = 2015"
query <- load_query(con,public,marginalization,options=the_options)
marginacion <- query %>% retrieve_result()
clear_results(con)
[1] TRUE
# TODO change this to SQL query
marginacion %<>% select(-CVE_ENT,-ENT,-MUN,-VP)
colnames(marginacion) <- c("CLAVE","pop_marg","illiteracy","elementary","no_sewage","no_electricity","no_water","overcrowding","dirt_floor","less_5k","less_2minwage","ovsd","ovsdse","im","gm","ind0a100","lugar","lugarest","YEAR")

# TODO set CLAVE as TEXT in marginalization schema for SQL ingest in pipeline
marginacion$CLAVE <- as.character(marginacion$CLAVE) %>% str_pad(width=5,pad="0")
col2num <- colnames(marginacion %>% select(-CLAVE,-gm))
marginacion[col2num] <- sapply(marginacion[col2num],as.numeric)

marginacion <- marginacion[!is.na(marginacion$gm),]

Join the three tables. I should run this in Postgres. TODO write SQL statement.

schools <- conapo %>% right_join(planea,by=c("NOM_ENT"="entidad","MUN"="municipio")) %>% left_join(marginacion, by="CLAVE") %>% ungroup(NOM_ENT)
schools$gm <- factor(schools$gm,levels=c("Muy bajo","Bajo","Medio","Alto","Muy alto"))
schools$tipo_escuela <- factor(schools$tipo_escuela,levels=c("General Pública","Técnica Pública","Telesecundaria","Privada","Comunitaria"))

This result is for a table in the document

marginacion %>% group_by(gm) %>% summarise(n())

How many schools

schools %>% group_by(gm) %>% summarise(n())
Factor `gm` contains implicit NA, consider using `forcats::fct_explicit_na`

The number of schools in a municipality.

schools_by_mun <- planea %>% distinct(ent,municipio,escuela,marginacion) %>% group_by(ent,municipio) %>% summarise(number=n())
num_schools <- schools_by_mun %>% group_by(number) %>% summarise(num_school = n())
schools$CLAVE %>% unique() %>% length()
conapo$CLAVE %>% unique() %>% length()

Fit porwer-law to number of schools

schools_power_law <- lm(log10(num_school) ~ log10(number),num_schools)
seq_schools <- seq(min(num_schools),max(num_schools))
adjusted_power <- 10^schools_power_law$coefficients[1]*seq_schools^schools_power_law$coefficients[2]
schools_power_law
the_p_law <- tibble(number=seq_schools,value=adjusted_power)
num_schools %<>% left_join(the_p_law,by="number")
ggplot(num_schools)+
  geom_bar(aes(x=number,y=num_school),stat = "identity")+
  geom_line(aes(x=number,y=value),color="red")+
  scale_x_continuous(name="Schools")+
  scale_y_continuous(name = "Municipalities",limits = c(0,250))+
  ggtitle("Municipalities by number of schools")+
  th

# This path should point to a place inside the docker container
ggsave("../../figs/powerlaw_school_mun.png",width = 15,height = 10,units = "cm")
ggplot(num_schools)+
  geom_point(aes(x=log10(number),y=log10(num_school)))+
  # geom_smooth(aes(x=log10(number),y=log10(num_school)),method = "glm")+
  geom_line(aes(x=log10(number),y=log10(value)),color="red")+
  scale_x_continuous(name="Schools")+
  scale_y_continuous(name = "Municipalities")+
  ggtitle("Municipalities by number of schools")+
  th

ggsave("../../figs/powerlaw_school_mun_log.png",width = 15,height = 10,units = "cm")

Number of evaluated students in language

sum(as.numeric(planea$evaluados_len))
[1] 1016087

Number of evaluated students in mathematics

sum(as.numeric(planea$evaluados_mat))
[1] 1012267

There’s a variable called representativity for both math and language but it’s unclear the percentage cut-off, then I will use the schools with above 50% of both tests

schools[(schools$repre_len == "NO") | (schools$repre_mat == "NO"),]
schools[(schools$porc_len > 50 & schools$porc_mat > 50),]
schools[!(schools$porc_len > 50 & schools$porc_mat > 50),]
schools[(schools$repre_len == "SÍ") & (schools$repre_mat == "SÍ"),]
schools
schools <- schools[(schools$porc_len > 50 & schools$porc_mat > 50),]
mun_schools <- schools %>%
  select(CLAVE,escuela,POP,marginacion,gm,elementary,illiteracy) %>%
  unique() %>% dplyr::group_by(CLAVE) %>%
  dplyr::summarise(num_school = n(),POB=unique(POP),elementary=unique(elementary),illiteracy=unique(illiteracy),gm=unique(gm)) %>%
  mutate(spc = num_school/(POB/100000))

Number of evaluated students in language

sum(as.numeric(schools$evaluados_len))
[1] 1008233

Number of evaluated students in mathematics

sum(as.numeric(schools$evaluados_mat))
[1] 1004766
unique((schools$clave)) %>% length()
[1] 31554
unique((schools$CLAVE)) %>% length()
[1] 1988
mar_scores <- schools %>% select(CLAVE,POP,illiteracy,elementary,I_porc_len,II_porc_len,III_porc_len,IV_porc_len,I_porc_mat,II_porc_mat,III_porc_mat,IV_porc_mat)
mar_scores %<>% group_by(CLAVE) %>% dplyr::summarise(num_school=n(),POP=unique(POP),illiteracy=unique(illiteracy),elementary=unique(elementary),I_len = mean(I_porc_len),II_len = mean(II_porc_len),III_len = mean(III_porc_len),IV_len = mean(IV_porc_len),
                                                    I_mat = mean(I_porc_mat),II_mat = mean(II_porc_mat),III_mat = mean(III_porc_mat),IV_mat = mean(IV_porc_mat))
ggplot(mun_schools)+
  geom_histogram(aes(spc),binwidth = 10,fill="#848256")+
  scale_x_continuous(name="Schools per 100,000 pop")+
  scale_y_continuous(name="Municipalities")+
  th

Translate some variables to english. There are some with peculiar name that shouldn’t be translated.

schools_tidy <- schools %>% select(tipo_escuela,gm,I_porc_len,II_porc_len,III_porc_len,IV_porc_len,I_porc_mat,II_porc_mat,III_porc_mat,IV_porc_mat)
schools_tidy %<>% pivot_longer(c(I_porc_len,II_porc_len,III_porc_len,IV_porc_len,I_porc_mat,II_porc_mat,III_porc_mat,IV_porc_mat))

unique(schools$tipo_escuela)
[1] General Pública Comunitaria     Técnica Pública Telesecundaria  Privada        
Levels: General Pública Técnica Pública Telesecundaria Privada Comunitaria
englishType <- c("Comunitaria","Public G.", "Private", "Public T.", "Telesecundaria")
spanishType <- c("Comunitaria","General Pública", "Privada", "Técnica Pública", "Telesecundaria")

englishM <- c("Very Low","Low", "Medium", "High", "Very High")
spanishM <- c("Muy bajo","Bajo", "Medio", "Alto", "Muy alto")

results <- c("Language I", "Language II", "Language III", "Language IV", "Math I", "Math II", "Math III", "Math IV")
resultados <- c("I_porc_len","II_porc_len","III_porc_len","IV_porc_len","I_porc_mat","II_porc_mat","III_porc_mat","IV_porc_mat")

schools_tidy$tipo_escuela <- plyr::mapvalues(schools_tidy$tipo_escuela, to=englishType, from=spanishType)
schools_tidy$name <- plyr::mapvalues(schools_tidy$name, to=results, from=resultados)
schools_tidy$gm <- plyr::mapvalues(schools_tidy$gm, to=englishM, from=spanishM)

# schools_tidy$tipo_escuela <- factor(schools_tidy$tipo_escuela,levels=c("General Pública","Técnica Pública","Telesecundatia","Privada","Comunitaria"))

Define the colors

altoc <- "#e3b23c"
bajoc <- "#725752"
medioc <- "#fe64a3"
maltoc <- "#78bc61"
mbajoc <- "#4f359b"
mt <- ggplot(schools_tidy)+
  geom_boxplot(aes(tipo_escuela,value,fill=gm),alpha=0.8)+
  scale_fill_manual(name = "Marginalization", values = c("High" = altoc, "Low" = bajoc, "Medium" = medioc, "Very High" = maltoc, "Very Low" = mbajoc),
                      labels = c("Very Low", "Low", "Medium", "High", "Very High"))+
  scale_x_discrete(name="School Type")+#, breaks = c("a","b","c","d","e"))+
  # scale_y_continuous(name = "Risk Aversion Measure", breaks = c(0,0.2,0.4,0.6,0.8,1), limits = c(-0.1,1.1))+
  scale_y_continuous(name = "Student percentage")+
  # theme_classic()+
  ggtitle("School-wise student percentage by achievement level")+
  th+
  theme(
    axis.text.x = element_text(angle = 0),
    panel.grid.minor = element_blank())

mt + facet_grid(vars(name))+
    theme(strip.background = element_rect(fill="#b53a31"))

ggsave("../../figs/achievement_levels.png",height = 20, width = 40, units = "cm")

Define plot colors

comc <- "#efecca"
privc <- "#db2b39"
pubgc <- "#f7ff58"
pubtc <- "#ff934f"
telec <- "#29335c"
mt <- ggplot(schools_tidy)+
  geom_boxplot(aes(gm,value,fill=tipo_escuela),alpha=0.8)+
  scale_fill_manual(name = "School Type", values = c("Comunitaria" = comc, "Private" = privc, "Public G." = pubgc, "Public T." = pubtc, "Telesecundaria" = telec))+
                      # labels = c("Low", "Very Low", "Medium", "High", "Very High"))+
  scale_x_discrete(name="Marginalization")+#, breaks = c("a","b","c","d","e"))+
  scale_y_continuous(name = "Student percentage")+
  ggtitle("School-wise student percentage by achievement level")+
  th+
  theme(
    axis.text.x = element_text(angle = 0),
    panel.grid.minor = element_blank())

mt + facet_grid(vars(name)) +
    theme(strip.background = element_rect(fill="#b53a31"))

ggsave("../../figs/achievement_levels_by_marginalization.png",height = 20, width = 40, units = "cm")
schools <- conapo %>% right_join(planea,by=c("NOM_ENT"="entidad","MUN"="municipio")) %>% left_join(marginacion, by="CLAVE")
unique(schools$tipo_escuela)
[1] "General Pública" "Comunitaria"     "Técnica Pública" "Telesecundaria"  "Privada"        
schools$gm <- factor(schools$gm,levels=c("Muy bajo","Bajo","Medio","Alto","Muy alto"))
ggplot(schools)+
  geom_boxplot(aes(tipo_escuela,IV_porc_mat,fill=gm),alpha=0.6)+
  th+
  theme(
    axis.text.x = element_text(angle = 0),
    panel.grid.minor = element_blank())

# ggsave("../../figs/achievement_levels_by_marginalization.png",height = 20, width = 40, units = "cm")

schools with 100% in IV level for mathematics

val100 <- mar_scores[order(mar_scores$IV_mat,decreasing = T),][1:4,]$CLAVE
schools[schools$CLAVE %in% val100,]

Load the indigenous population and joint information with the test.

the_options <- "WHERE \"MPO\" != '000'"
query <- load_query(con,public,indigenous_language,options = the_options)
indig <- query %>% retrieve_result()
clear_results(con)
[1] TRUE
indig %<>% mutate(CLAVE=paste0(ENT,MPO),ph_analf = hANALFA/IPOB_INDI, pm_analf = mANALFA/IPOB_INDI)
mar_scores %<>% left_join(indig,by="CLAVE") %<>% mutate(prop_indi = IPOB_INDI/POP)
mar_scores %<>% left_join(schools %>% select(CLAVE,gm)) %>% unique()
Adding missing grouping variables: `NOM_ENT`
Joining, by = "CLAVE"
mar_scores %<>% mutate(p_len = (IPhli/POP))

Plot the percentage of indigenous population by level of marginalization.

englishType <- c("Comunitaria","Public G.", "Private", "Public T.", "Telesecundaria")
spanishType <- c("Comunitaria","General Pública", "Privada", "Técnica Pública", "Telesecundaria")

mar_scores$gm <- plyr::mapvalues(mar_scores$gm, to=englishM, from=spanishM)
The following `from` values were not present in `x`: Muy bajo, Bajo, Medio, Alto, Muy alto
mar_scores <- mar_scores[!is.na(mar_scores$gm),]

ggplot(mar_scores)+
  geom_boxplot(aes(gm,prop_indi,fill=gm),alpha=0.8)+
  scale_fill_manual(name = "Marginalization", values = c("High" = altoc, "Low" = bajoc, "Medium" = medioc, "Very High" = maltoc, "Very Low" = mbajoc),
                      labels = c("Very Low", "Low", "Medium", "High", "Very High"))+
  scale_x_discrete(name="Marginalization")+#, breaks = c("a","b","c","d","e"))+
  scale_y_continuous(name = "Percentage of indigenous population",limits = c(0,1))+
  ggtitle("Indigenous population by marginalization")+
    th

ggsave("../../figs/indi_vs_marg.png",height = 20, width = 40, units = "cm")

Some variables that won’t make it into the document

ggplot(mar_scores)+
  geom_point(aes((prop_indi),(illiteracy/100)))+
  geom_smooth(aes((prop_indi),(illiteracy/100)),method = "lm",color="red")+
  scale_y_continuous(name="Illiteracy")+
  scale_x_continuous(name="Indigenous Population")+
  th

As expected the ratio of indigenous population in a municipality correlates perfectly with the percentage of inhabitants that speak a indigenous language.

ggplot(mar_scores)+
  geom_point(aes((prop_indi),(p_len)))+
  geom_smooth(aes((prop_indi),(p_len)),method = "lm",color="red")+
  scale_y_log10(name="Indigenous Language")+
  scale_x_log10(name="Indigenous Population")+
  th

The more indigenous more illiteracy.

ggplot(mar_scores)+
  geom_point(aes((p_len),(illiteracy/100)))+
  geom_smooth(aes((p_len),(illiteracy/100)),method = "lm",color="red")+
  scale_y_continuous(name="Illiteracy")+
  scale_x_continuous(name="Indigenous Language")+
  th

The percentage of indigenous population correlates possitevily with the percentage of level 1 achievement.

lm(I_len/100 ~ p_len, mar_scores)
ggplot(mar_scores)+
  geom_point(aes((p_len),I_len/100))+
  geom_smooth(aes((p_len),I_len/100),method = "lm")+
  scale_x_continuous(name = "Indigenous language speakers")+
  scale_y_continuous(name = "Language I achievement")+
  th

ggsave("../../figs/ILan_vs_indiLang.png",height = 20, width = 20, units = "cm")

While for the level IV it has a negative correlation

lm(IV_len/100 ~ p_len, mar_scores)
ggplot(mar_scores)+
  geom_point(aes((p_len),IV_len/100))+
  geom_smooth(aes((p_len),IV_len/100),method = "lm")+
  scale_x_continuous(name = "Indigenous language speakers")+
  scale_y_continuous(name = "Language IV achievement")+
  th
ggsave("../../figs/IVLan_vs_indiLang.png",height = 20, width = 20, units = "cm")
# mar_tidy <- mar_scores %>% select(tipo_escuela,gm,I_porc_len,II_porc_len,III_porc_len,IV_porc_len,I_porc_mat,II_porc_mat,III_porc_mat,IV_porc_mat)
mar_tidy <- mar_scores %>% pivot_longer(c(I_len,II_len,III_len,IV_len,I_mat,II_mat,III_mat,IV_mat)) %>% unique()

How does language and math level I correlate in a highly marginalized municipality?

ggplot(mar_scores[mar_scores$gm == "Very High",])+
  coord_fixed()+
  geom_abline()+
  scale_y_continuous(limits = c(0,100))+
  scale_x_continuous(limits = c(0,100))+
  geom_point(aes(I_len,I_mat))+
  th

what about a very low marginalized area?

ggplot(mar_scores[mar_scores$gm == "Very Low",])+
  coord_fixed()+
  geom_abline()+
  scale_y_continuous(limits = c(0,100))+
  scale_x_continuous(limits = c(0,100))+
  geom_point(aes(I_len,I_mat))+
  th

mt <- ggplot(mar_scores)+
  geom_point(aes(log10(I_len),log10(I_mat),color=gm))+
  scale_color_manual(name = "Marginalization", values = c("High" = altoc, "Low" = bajoc, "Medium" = medioc, "Very High" = maltoc, "Very Low" = mbajoc),
                      labels = c("Very Low", "Low", "Medium", "High", "Very High"))+
  coord_fixed()+
  geom_abline()+
  scale_x_log10(name = "percentage language")+#,limits = c(0,100))+
  scale_y_log10(name = "percentage mathematics")+#,limits = c(0,100))+
  ggtitle("Achievement Level I by marginalization")+
  th+
  theme(
    axis.text.x = element_text(angle = 0),
    panel.grid.minor = element_blank(),
    legend.position = "none")

mt + facet_grid(cols=vars(gm)) +
    theme(strip.background = element_rect(fill="#b53a31"))

ggsave("../../figs/math_vs_language_I_log.png",height = 20, width = 40, units = "cm")
lm((I_mat) ~ (I_len),mar_scores[mar_scores$gm == "Very Low",])
lm((I_mat) ~ (I_len),mar_scores[mar_scores$gm == "Very High",])
ggplot(mar_scores)+
  geom_point(aes(log10(IV_len),log10(IV_mat)))+
  coord_fixed()+
  geom_abline()+
  scale_x_continuous(name = "percentage language")+#,limits = c(0,100))+
  scale_y_continuous(name = "percentage mathematics")+#,limits = c(0,100))+
  ggtitle("Achievement Level IV")+
  th+
  theme(
    axis.text.x = element_text(angle = 0),
    panel.grid.minor = element_blank(),
    legend.position = "none")


ggsave("../../figs/math_vs_language_VI_all_log.png",height = 20, width = 40, units = "cm")
# ggsave("~/Documents/UVM/courses/DS2/assignments/HW8/math_vs_language_IV_all.png",height = 20, width = 20, units = "cm")
mt <- ggplot(mar_scores)+
  geom_point(aes(log10(IV_len),log10(IV_mat),color=gm))+
  scale_color_manual(name = "Marginalization", values = c("High" = altoc, "Low" = bajoc, "Medium" = medioc, "Very High" = maltoc, "Very Low" = mbajoc),
                      labels = c("Very Low", "Low", "Medium", "High", "Very High"))+
  coord_fixed()+
  geom_abline()+
  scale_x_log10(name = "percentage language")+#,limits = c(0,100))+
  scale_y_log10(name = "percentage mathematics")+#,limits = c(0,100))+
  ggtitle("Achievement Level IV by marginalization")+
  th+
  theme(
    axis.text.x = element_text(angle = 0),
    panel.grid.minor = element_blank(),
    legend.position = "none")

mt + facet_grid(cols=vars(gm)) +
    theme(strip.background = element_rect(fill="#b53a31"))

ggsave("../../figs/math_vs_language_IV_log.png",height = 20, width = 40, units = "cm")
lm((IV_mat) ~ (IV_len),mar_scores[mar_scores$gm == "Very Low",])
lm((IV_mat) ~ (IV_len),mar_scores[mar_scores$gm == "Very High",])
mt <- ggplot(mar_scores)+
  geom_point(aes((IV_len),(I_mat),color=gm))+
  scale_color_manual(name = "Marginalization", values = c("High" = altoc, "Low" = bajoc, "Medium" = medioc, "Very High" = maltoc, "Very Low" = mbajoc),
                      labels = c("Very Low", "Low", "Medium", "High", "Very High"))+
  coord_fixed()+
  geom_abline()+
  # scale_x_continuous(name = "percentage language",limits = c(0,100))+
  # scale_y_continuous(name = "percentage mathematics",limits = c(0,100))+
  ggtitle("Achievement Level IV by marginalization")+
  th+
  theme(
    axis.text.x = element_text(angle = 0),
    panel.grid.minor = element_blank(),
    legend.position = "none")

mt + facet_grid(cols=vars(gm)) +
    theme(strip.background = element_rect(fill="#b53a31"))

# ggsave("~/Documents/UVM/courses/DS2/assignments/HW6/math_vs_language_IV.png",height = 20, width = 40, units = "cm")
mt <- ggplot(mar_scores)+
  geom_point(aes((prop_indi),(IV_len),color=gm),alpha=0.6)+
  # geom_density(aes(pm_analf),)+
  # geom_point(aes(ph_analf,prop_indi,color=gm),alpha=0.4)+
  scale_color_manual(name = "Marginalization", values = c("High" = altoc, "Low" = bajoc, "Medium" = medioc, "Very High" = maltoc, "Very Low" = mbajoc),
                      labels = c("Very Low", "Low", "Medium", "High", "Very High"))+
  # coord_fixed()+
  # geom_abline()+
  # scale_x_continuous(name = "percentage language",limits = c(0,1))+
  # scale_y_continuous(name = "percentage mathematics",limits = c(0,100))+
  scale_x_log10()+
  scale_y_log10()+
  ggtitle("Achievement Level IV by marginalization")+
  th+
  theme(
    axis.text.x = element_text(angle = 0),
    panel.grid.minor = element_blank(),
    legend.position = "none")

mt + facet_grid(cols=vars(gm)) +
    theme(strip.background = element_rect(fill="#b53a31"))

# ggsave("~/Documents/UVM/courses/DS2/assignments/HW6/math_vs_language_IV.png",height = 20, width = 40, units = "cm")

Maps and things

I defined a function to get the geometries in an easy way. TODO: update fortify with broom

indig_pop <- indig %>% left_join(conapo,by="CLAVE")
indig_pop %<>% mutate(p_indig = IPOB_INDI/POP)
municipios_pop <- geom_muni %>% left_join(indig_pop, by = c("CVEGEO"="CLAVE"))
geom_muni$CVEGEO %>% unique() %>% length()

We have 7 municipalities without indigenous information, I don’t know if it is because they were instituted between 2015 and 2018, which is the last CONAPO available data and the geometry data that I have. (2456 to 2463, as in 2020 there are 2464)

ggplot()+
  geom_polygon(data = municipios_pop,
               aes(long,lat,label=CVEGEO,group=group,
                   fill=p_indig*100),color="grey",size=0.1)+
  coord_map(projection = "mercator")+
  scale_fill_continuous(name="Percentage",low="white", high="#0f5a5e",
                       guide="colorbar",limits=c(0,100))+
  labs(title="Indigenous Population")+
  theme(#panel.grid.major.y = element_line(color = "gray"),
        panel.grid.major.y = element_blank(),
        text = element_text(color = "gray20"),
        panel.background = element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        axis.text.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.x = element_blank(),
        axis.ticks.y = element_blank(),
        legend.box = "horizontal",
        legend.text = element_text(size = 10),
        plot.caption = element_text(hjust=0),
        plot.title = element_text(size = 16, face = "bold",hjust=0.39))

ggsave("../../figs/map_indigenous.png",height = 30, width = 30, units = "cm")
municipios_nal <- geom_muni %>% left_join(mar_scores, by = c("CVEGEO"="CLAVE"))

A tilt in the map allows a better stacking

# Shear/scale matrix [[2,1],[0,1]] obtained by some trial and error:
sm <- matrix(c(1,-0.3,0,1),2,2)
# Get transformed coordinates:
xy <- as.matrix(municipios_nal[,c("long","lat")]) %*% sm
# Add xy as extra columns in fortified data:
municipios_nal$x <- xy[,1]; municipios_nal$y = xy[,2]
min(municipios_nal$x)
max(municipios_nal$x)

max(municipios_nal$y) - min(municipios_nal$y)
min(municipios_nal$y)
mean(municipios_nal$y)

Stacked map for the 4 levels of math

p <- ggplot() +
  geom_polygon(data = municipios_nal, aes(x, y,label=CVEGEO, group=group,fill=(I_mat)),color="white",size=0.01)+
  annotate("text",x=min(municipios_nal$x)+10,y=mean(municipios_nal$y),color="gray35",label="Level I")+
  geom_polygon(data = municipios_nal, aes(x, y-14,label=CVEGEO, group=group,fill=(II_mat)),color="white",size=0.01)+
  annotate("text",x=min(municipios_nal$x)+10,y=mean(municipios_nal$y-14),color="gray35",label="Level II")+
  geom_polygon(data = municipios_nal, aes(x, y-28,label=CVEGEO, group=group,fill=(III_mat)),color="white",size=0.01)+
  annotate("text",x=min(municipios_nal$x)+10,y=mean(municipios_nal$y-28),color="gray35",label="Level III")+
  geom_polygon(data = municipios_nal, aes(x, y-42,label=CVEGEO, group=group,fill=(IV_mat)),color="white",size=0.01)+
  annotate("text",x=min(municipios_nal$x)+10,y=mean(municipios_nal$y-42),color="gray35",label="Level IV")+
  labs(title="Mathematics")+
  scale_fill_continuous(name = "Mean percentage",low="white", high="#5A0D35", guide="colorbar")+#,na.value="#f2f2f2")+
  coord_map(projection = "mercator")+
  theme(#panel.grid.major.y = element_line(color = "gray"),
        panel.grid.major.y = element_blank(),
        text = element_text(color = "gray20"),
        panel.background = element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        axis.text.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.x = element_blank(),
        axis.ticks.y = element_blank(),
        # legend.box = "horizontal",
        legend.text = element_text(size = 10),
        plot.caption = element_text(hjust=0),
        plot.title = element_text(size = 16, face = "bold",hjust=0.39))

ggsave("../../figs/stacked_math.png",p,height = 30, width = 30, units = "cm")

and language

p <- ggplot() +
  geom_polygon(data = municipios_nal, aes(x, y,label=CVEGEO, group=group,fill=(I_len)),color="white",size=0.01)+
  annotate("text",x=min(municipios_nal$x)+10,y=mean(municipios_nal$y),color="gray35",label="Level I")+
  geom_polygon(data = municipios_nal, aes(x, y-14,label=CVEGEO, group=group,fill=(II_len)),color="white",size=0.01)+
  annotate("text",x=min(municipios_nal$x)+10,y=mean(municipios_nal$y-14),color="gray35",label="Level II")+
  geom_polygon(data = municipios_nal, aes(x, y-28,label=CVEGEO, group=group,fill=(III_len)),color="white",size=0.01)+
  annotate("text",x=min(municipios_nal$x)+10,y=mean(municipios_nal$y-28),color="gray35",label="Level III")+
  geom_polygon(data = municipios_nal, aes(x, y-42,label=CVEGEO, group=group,fill=(IV_len)),color="white",size=0.01)+
  annotate("text",x=min(municipios_nal$x)+10,y=mean(municipios_nal$y-42),color="gray35",label="Level IV")+
  labs(title="Language")+
  scale_fill_continuous(name = "Mean percentage",low="white", high="#5a5a0d", guide="colorbar")+#,na.value="#f2f2f2")+
  coord_map(projection = "mercator")+
  theme(#panel.grid.major.y = element_line(color = "gray"),
        panel.grid.major.y = element_blank(),
        text = element_text(color = "gray20"),
        panel.background = element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        axis.text.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.x = element_blank(),
        axis.ticks.y = element_blank(),
        # legend.box = "horizontal",
        legend.text = element_text(size = 10),
        plot.caption = element_text(hjust=0),
        plot.title = element_text(size = 16, face = "bold",hjust=0.39))

ggsave("../../figs/stacked_language.png",p,height = 30, width = 30, units = "cm")

I want to compute the density of schools by square kilometer

query <- load_table(con,public,geom_muni)
muni_df <- query %>% retrieve_result()
clear_results(con)
[1] TRUE

Build the polygons and compute the area in Km^2

polman <- lapply(muni_df$WKT,rgeos::readWKT)
rgeos::gUnion(polman[[1]],polman[[2]])
class       : SpatialPolygons 
features    : 1 
extent      : -102.5831, -101.8542, 21.62227, 22.30607  (xmin, xmax, ymin, ymax)
crs         : NA 
length(polman)
[1] 2463
the_areas <- sapply(polman,geosphere::areaPolygon)
areas_df <- tibble(CVEGEO=muni_df$CVEGEO,area=the_areas/1000000)

Now we need the number of Schools but not from the test, we’ll use the data from Consejo Nacional de Fomento Educativo (CONAFE) through the Censo de Escuelas, Maestros y Alumnos de Educación Básica y Especial (CEMABE) Nop, now I now that CONAFE is a program to contribute children in highly marginalized areas to conclude their basic comunitary education, with economical support to educational figures and scholarly supplies to students. It would be interesting to analize this into deep!!!

This is the number of schools in the planea set

unique(planea$clave) %>% length()
schools_mun <- schools %>% group_by(CLAVE) %>% summarise(num=n())
areas_df %<>% left_join(schools_mun,by=c("CVEGEO"="CLAVE"))
areas_df %<>% mutate(density = num/area)

The density is log-normal As well as the test results. Should consider this when doing regressions.

ggplot(areas_df)+
  geom_histogram(aes(log10(density)))+
  th
geom_mun_school <- geom_muni %>% left_join(areas_df, by = c("CVEGEO"))
library(latex2exp)
ggplot()+
  geom_polygon(data = geom_mun_school,
               aes(long,lat,label=CVEGEO,group=group,
                   fill=(density)),color="grey",size=0.01)+
  coord_map(projection = "mercator")+
  # scale_fill_continuous(name="Percentage",low="white", high="#4c1262", guide="colorbar")+#,limits=c(0,100))+
  scale_fill_gradient(trans="log10",name="Density",low="white", high="#4c1262", guide="colorbar",labels=c(0,0.5,1,1.5,2,2.5))+#,limits=c(0,100))+
  labs(title=TeX("Schools by $Km^2$"))+
  theme(#panel.grid.major.y = element_line(color = "gray"),
        panel.grid.major.y = element_blank(),
        text = element_text(color = "gray20"),
        panel.background = element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        axis.text.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks.x = element_blank(),
        axis.ticks.y = element_blank(),
        legend.box = "horizontal",
        legend.text = element_text(size = 10),
        plot.caption = element_text(hjust=0),
        plot.title = element_text(size = 16, face = "bold",hjust=0.39))

ggsave("../../figs/map_school_density.png",height = 30, width = 30, units = "cm")

I think I haven’t written the ingestion code for the schools into the sql database. TODO Although I’m not using it.

conafe <- read_delim("/home/ollin/Documents/UVM/courses/DS2/project/planea/data/clean/tr_conafe.csv",delim="|")
conafe %<>% mutate(CVEGEO=paste0(ENT,MUN))
conafe %>% group_by(CVEGEO) %>% summarise(num = n())

Maps are cool but we need other stuff, let’s make ridges for achievement

library(ggridges)
mar_tidy <- mar_scores %>% pivot_longer(c(I_len,II_len,III_len,IV_len,I_mat,II_mat,III_mat,IV_mat)) %>% unique()

First for all

ggplot(mar_tidy[grep("mat",mar_tidy$name),], aes(x = value, y = name, height = stat(density))) +
  geom_density_ridges(stat = "density")+
  scale_x_log10(name="Percentage")+
  scale_y_discrete(name="Level")+
  th
ggsave("../../figs/ridges_math_log.png",height = 30, width = 30, units = "cm")

We conducted an Anderson-Darling test to assess the normality of distributions along achievement levels

compute how likely are them to be drawned by a normal distribution

This means that our values are not normal

compute how likely are them to be drawned by a the same distribution

combos <- combn(4,2)
plyr::adply(combos, 2, function(x) {
  test <- t.test(Datas[, as.numeric(x[1])], Data[, as.numeric(x[2])],alternative = "t")

  out <- data.frame("var1" = colnames(Data)[x[1]]
                    , "var2" = colnames(Data[x[2]])
                    , "t.value" = sprintf("%.5f", test$statistic)
                    ,  "df"= test$parameter
                    ,  "p.value" = test$p.value#sprintf("%.5f", test$p.value)
                    )
  return(out)

})
# mar_scores %>%
#   dplyr::select(I_len,II_len,III_len,IV_len) %>% 
#   dplyr::select_if(is.numeric) %>% 
#   purrr::map_df(~ broom::tidy(t.test(. ~ grp)), .id = 'var')

compute how likely are them to be have the same mean

compute how likely are them to have the same mean

combos <- combn(4,2)
plyr::adply(combos, 2, function(x) {
  test <- t.test(Data[, as.numeric(x[1])], Data[, as.numeric(x[2])],alternative = "t")

  out <- data.frame("var1" = colnames(Data)[x[1]]
                    , "var2" = colnames(Data[x[2]])
                    , "t.value" = sprintf("%.5f", test$statistic)
                    ,  "df"= test$parameter
                    ,  "p.value" = test$p.value#sprintf("%.5f", test$p.value)
                    )
  return(out)

})
# mar_scores %>%
#   dplyr::select(I_len,II_len,III_len,IV_len) %>% 
#   dplyr::select_if(is.numeric) %>% 
#   purrr::map_df(~ broom::tidy(t.test(. ~ grp)), .id = 'var')

Then the facets by marginalization level

ggplot(mar_tidy[grep("mat",mar_tidy$name),], aes(x = value, y = name,fill=gm,alpha=0.6, height = stat(density))) +
  scale_fill_manual(name = "Marginalization", values = c("High" = altoc, "Low" = bajoc, "Medium" = medioc, "Very High" = maltoc, "Very Low" = mbajoc),
                      labels = c("Very Low", "Low", "Medium", "High", "Very High"))+
  geom_density_ridges(stat = "density")+
  scale_x_log10()+
  th+
  theme(
    axis.text.x = element_text(angle = 0),
    panel.grid.minor = element_blank(),
    legend.position = "none")+
  facet_grid(~gm)+
  theme(strip.background = element_rect(fill="#b53a31"))

We test the homogeneity of variances with an F-test because we want to see how well does an achievement level in one test explains the other.

tocomp <- mar_scores %>%
  group_by(gm) %>% 
  select(gm,I_len,I_mat,IV_len,IV_mat)
logss <- sapply(tocomp %>% ungroup(gm) %>% select(-gm), function(x) log10(as.numeric(x)) )
tocomp[2:5] <- logss
tocomp <- tocomp[apply(tocomp %>% ungroup(gm) %>% select(-gm), 1, function(row) all(is.finite(row))),]
# tocomp <- tocomp %>% sample_n(250,replace = F)

tocomp %>%
  group_by(gm) %>% 
  summarise(pval = var.test(I_len,I_mat,alternative="t")$p.value)

Use the Fligner-Killen median test to compare the homogeneity of variances

But what about school type

mar_tidy <- mar_scores %>% pivot_longer(c(I_len,II_len,III_len,IV_len,I_mat,II_mat,III_mat,IV_mat)) %>% unique()
mar_scores %<>% left_join(schools %>% select(CLAVE,tipo_escuela),by="CLAVE") %>% unique()
mar_tidy2 <- mar_scores %>% pivot_longer(c(I_len,II_len,III_len,IV_len,I_mat,II_mat,III_mat,IV_mat)) %>% unique()
ggplot(mar_tidy2[grepl("I_",mar_tidy2$name),], aes(x = (value), y = name,fill=tipo_escuela,alpha=0.6, height = stat(density))) +
  # scale_fill_manual(name = "Marginalization", values = c("High" = altoc, "Low" = bajoc, "Medium" = medioc, "Very High" = maltoc, "Very Low" = mbajoc),
  #                       labels = c("Very Low", "Low", "Medium", "High", "Very High"))+
    geom_density_ridges(stat = "density")+
  scale_x_log10()+
    th+
    theme(
      axis.text.x = element_text(angle = 0),
      panel.grid.minor = element_blank(),
      legend.position = "none")+
    facet_grid(~tipo_escuela)+
    theme(strip.background = element_rect(fill="#b53a31"))

Let’s check the Bayesian networks

Puerto Morelos became municipality in 2011

encode_ordinal <- function(x, order = unique(x)) {
  x <- as.numeric(factor(x, levels = order, exclude = NULL))
  x
}
schools <- schools %>% select(-ovsd,-ovsdse,-ind0a100)
Error: Can't subset columns that don't exist.
x The column `ovsd` doesn't exist.
for_bn <- schools %>% left_join(indig %>% select(CLAVE,IPOB_INDI,IPhli))
Joining, by = "CLAVE"
for_bn <- for_bn %>% mutate(p_indig = IPOB_INDI/pop_marg,lang_indig=IPhli/IPOB_INDI)
for_bn %<>% left_join(areas_df,by="CLAVE")

for_bn[!complete.cases(for_bn),]$IPOB_INDI <- 0
for_bn[!complete.cases(for_bn),]$p_indig <- 0
databn <- for_bn %>% ungroup(CLAVE,NOM_ENT) %>% select(I_porc_len,II_porc_len,III_porc_len,IV_porc_len,I_porc_mat,II_porc_mat,III_porc_mat,IV_porc_mat,turno,gm,tipo_escuela,illiteracy,elementary,no_sewage,no_electricity,no_water,overcrowding,dirt_floor,less_5k,less_2minwage,im,lugar,lugarest,IPOB_INDI,p_indig,area,num,density,lang_indig)
databn

# I should automate this
databn$turno <- encode_ordinal(databn$turno)
databn$gm <- encode_ordinal(databn$gm)
databn$tipo_escuela <- encode_ordinal(databn$tipo_escuela)

# databn$turno <- as.double(databn$turno)
# databn$gm <- as.double(databn$gm)
# databn$num <- as.double(databn$num)
# databn$tipo_escuela <- as.double(databn$tipo_escuela)

databn2 <- databn

databn$I_porc_len <- log10(databn$I_porc_len)
databn$II_porc_len <- log10(databn$II_porc_len)
databn$III_porc_len <- log10(databn$III_porc_len)
databn$IV_porc_len <- log10(databn$IV_porc_len)

databn$I_porc_mat <- log10(databn$I_porc_mat)
databn$II_porc_mat <- log10(databn$II_porc_mat)
databn$III_porc_mat <- log10(databn$III_porc_mat)
databn$IV_porc_mat <- log10(databn$IV_porc_mat)

databn$density <- log10(databn$density)
databn$p_indig <- log10(databn$p_indig)
databn$lang_indig <- log10(databn$lang_indig)

databn_finite <- databn[apply(databn, 1, function(row) all(is.finite(row))),]
databn_finite <- databn[apply(databn %>% select(-turno,-gm,-num,-tipo_escuela), 1, function(row) all(is.finite(row))),]
databn_finite %<>% select(-im,-lugar,-lugarest,-IPOB_INDI,-area,-num)

Delete the highly correlated variables

set.seed(42)
corrM <- cor(databn_finite[,c(9:22)])
highlyCorrelated <- findCorrelation(corrM, cutoff=0.50)
colnames(databn_finite[,c(9:22)])[highlyCorrelated]
[1] "gm"             "illiteracy"     "elementary"     "less_2minwage"  "dirt_floor"     "less_5k"        "no_electricity"
databn_finite %<>% select(-illiteracy,-elementary,-less_2minwage,-dirt_floor,-less_5k)
databn_finite %<>% select(-gm)

Variable selection

# control <- trainControl(method="cv", number=10)
# model <- caret::train(I_porc_len~., data=databn_finite, method="cforest", preProcess="scale", trControl=control)
# # estimate variable importance
# importance <- varImp(model, scale=FALSE)
# # summarize importance
# print(importance)
# # plot importance
# plot(importance)
# set.seed(42)
# databn_finite2 <- as.data.frame(databn_finite)
# #Controlling the feature selection, using default functions, with 10 folds cross validation
# control <- rfeControl(functions=rfFuncs, method="cv", number=10)
# # run the RFE algorithm
# # using recursive feature elimination or backwards selection
# # let's check first if it works the same for each level and test
# results <- rfe(x=databn_finite2[,c(1,10:19)], y=databn_finite2[,2], rfeControl=control)
# # results <- rfe(databn[,c(1,10:27)], databn[,n], sizes=c(1:18), rfeControl=control)
# # summarize the results
# print(results)
# # list the chosen features
# predictors(results)
# # plot the results
# plot(results, type=c("g", "o"))

I haven’t figured how to save this graphs with the chunk output inline in a notebook, so we have to dissable it to run and save the plots

for (i in 1:7){
  bnf <- bnlearn::h2pc(databn_finite[,c(i,9:17)])
  png(paste0("/figs/graph_",i,".png"))
  plot(graphviz.plot(bnf, shape = "ellipse"))
  dev.off()
  str.diff <- bnlearn::boot.strength(databn_finite[,c(i,9:17)],R=20,algorithm="h2pc")
  avg.diff <- averaged.network(str.diff)#,threshold = 0.7)
  png(paste0("/figs/graph_",i,"_boots.png"))
  plot(graphviz.plot(avg.diff, shape = "ellipse"))
  dev.off()
}
bnf <- bnlearn::h2pc(databn_finite[,c(1,9:17)])
  str.diff <- bnlearn::boot.strength(databn_finite[,c(1,9:17)],R=20,algorithm="h2pc")
  avg.diff <- averaged.network(str.diff)#,threshold = 0.7)
  plot(graphviz.plot(avg.diff, shape = "ellipse"))

fitted

  Bayesian network parameters

  Parameters of node I_porc_len (Gaussian distribution)

Conditional density: I_porc_len | turno + tipo_escuela + no_sewage + no_water + overcrowding + density
Coefficients:
 (Intercept)         turno  tipo_escuela     no_sewage      no_water  overcrowding       density  
 1.208965353   0.048668605  -0.063296901   0.003149070   0.002215214   0.005437790  -0.045189000  
Standard deviation of the residuals: 0.286911 

  Parameters of node turno (Gaussian distribution)

Conditional density: turno | tipo_escuela
Coefficients:
 (Intercept)  tipo_escuela  
  1.43415348   -0.07058727  
Standard deviation of the residuals: 0.6036932 

  Parameters of node tipo_escuela (Gaussian distribution)

Conditional density: tipo_escuela
Coefficients:
(Intercept)  
   3.053762  
Standard deviation of the residuals: 1.606159 

  Parameters of node no_sewage (Gaussian distribution)

Conditional density: no_sewage | overcrowding
Coefficients:
 (Intercept)  overcrowding  
  -3.5899777     0.2001326  
Standard deviation of the residuals: 3.575512 

  Parameters of node no_electricity (Gaussian distribution)

Conditional density: no_electricity | no_sewage + no_water
Coefficients:
(Intercept)    no_sewage     no_water  
 0.03559482   0.19572710   0.07582562  
Standard deviation of the residuals: 1.429915 

  Parameters of node no_water (Gaussian distribution)

Conditional density: no_water | turno
Coefficients:
(Intercept)        turno  
  5.7588547   -0.8451049  
Standard deviation of the residuals: 7.236526 

  Parameters of node overcrowding (Gaussian distribution)

Conditional density: overcrowding | no_water
Coefficients:
(Intercept)     no_water  
 25.0455778    0.5767251  
Standard deviation of the residuals: 8.071349 

  Parameters of node p_indig (Gaussian distribution)

Conditional density: p_indig | no_water + overcrowding + density + lang_indig
Coefficients:
 (Intercept)      no_water  overcrowding       density    lang_indig  
 -1.13032950    0.01086504    0.02940867    0.29089930    2.84358295  
Standard deviation of the residuals: 0.4536688 

  Parameters of node density (Gaussian distribution)

Conditional density: density | no_sewage + no_electricity + overcrowding + lang_indig
Coefficients:
   (Intercept)       no_sewage  no_electricity    overcrowding      lang_indig  
   -0.66960911     -0.02684206     -0.03848719     -0.01980660     -0.90658897  
Standard deviation of the residuals: 0.6659899 

  Parameters of node lang_indig (Gaussian distribution)

Conditional density: lang_indig | no_electricity
Coefficients:
   (Intercept)  no_electricity  
    -0.3915927       0.0110290  
Standard deviation of the residuals: 0.08323559 
graphviz.plot(bnf, shape = "ellipse")
fitted

  Bayesian network parameters

  Parameters of node IV_porc_len (Gaussian distribution)

Conditional density: IV_porc_len | turno + tipo_escuela + overcrowding + density
Coefficients:
 (Intercept)         turno  tipo_escuela  overcrowding       density  
 0.959579959  -0.049578803   0.082064268  -0.007839039   0.038389871  
Standard deviation of the residuals: 0.322551 

  Parameters of node turno (Gaussian distribution)

Conditional density: turno | tipo_escuela + no_water
Coefficients:
 (Intercept)  tipo_escuela      no_water  
 1.457507595  -0.069703101  -0.005509427  
Standard deviation of the residuals: 0.6023956 

  Parameters of node tipo_escuela (Gaussian distribution)

Conditional density: tipo_escuela | no_water
Coefficients:
(Intercept)     no_water  
3.016563264  0.007865988  
Standard deviation of the residuals: 1.605212 

  Parameters of node no_sewage (Gaussian distribution)

Conditional density: no_sewage | overcrowding
Coefficients:
 (Intercept)  overcrowding  
  -3.5899777     0.2001326  
Standard deviation of the residuals: 3.575512 

  Parameters of node no_electricity (Gaussian distribution)

Conditional density: no_electricity | no_sewage + no_water
Coefficients:
(Intercept)    no_sewage     no_water  
 0.03559482   0.19572710   0.07582562  
Standard deviation of the residuals: 1.429915 

  Parameters of node no_water (Gaussian distribution)

Conditional density: no_water
Coefficients:
(Intercept)  
   4.729013  
Standard deviation of the residuals: 7.254814 

  Parameters of node overcrowding (Gaussian distribution)

Conditional density: overcrowding | no_water
Coefficients:
(Intercept)     no_water  
 25.0455778    0.5767251  
Standard deviation of the residuals: 8.071349 

  Parameters of node p_indig (Gaussian distribution)

Conditional density: p_indig | no_water + overcrowding + density + lang_indig
Coefficients:
 (Intercept)      no_water  overcrowding       density    lang_indig  
 -1.13032950    0.01086504    0.02940867    0.29089930    2.84358295  
Standard deviation of the residuals: 0.4536688 

  Parameters of node density (Gaussian distribution)

Conditional density: density | no_sewage + no_electricity + overcrowding + lang_indig
Coefficients:
   (Intercept)       no_sewage  no_electricity    overcrowding      lang_indig  
   -0.66960911     -0.02684206     -0.03848719     -0.01980660     -0.90658897  
Standard deviation of the residuals: 0.6659899 

  Parameters of node lang_indig (Gaussian distribution)

Conditional density: lang_indig | no_electricity
Coefficients:
   (Intercept)  no_electricity  
    -0.3915927       0.0110290  
Standard deviation of the residuals: 0.08323559 
graphviz.plot(bnf, shape = "ellipse")
fitted

  Bayesian network parameters

  Parameters of node I_porc_mat (Gaussian distribution)

Conditional density: I_porc_mat | turno + tipo_escuela + overcrowding + lang_indig
Coefficients:
 (Intercept)         turno  tipo_escuela  overcrowding    lang_indig  
 1.681751189   0.021759359  -0.056877065   0.001356611  -0.068820838  
Standard deviation of the residuals: 0.2446307 

  Parameters of node turno (Gaussian distribution)

Conditional density: turno | tipo_escuela
Coefficients:
 (Intercept)  tipo_escuela  
  1.43415348   -0.07058727  
Standard deviation of the residuals: 0.6036932 

  Parameters of node tipo_escuela (Gaussian distribution)

Conditional density: tipo_escuela
Coefficients:
(Intercept)  
   3.053762  
Standard deviation of the residuals: 1.606159 

  Parameters of node no_sewage (Gaussian distribution)

Conditional density: no_sewage | overcrowding
Coefficients:
 (Intercept)  overcrowding  
  -3.5899777     0.2001326  
Standard deviation of the residuals: 3.575512 

  Parameters of node no_electricity (Gaussian distribution)

Conditional density: no_electricity | no_sewage + no_water
Coefficients:
(Intercept)    no_sewage     no_water  
 0.03559482   0.19572710   0.07582562  
Standard deviation of the residuals: 1.429915 

  Parameters of node no_water (Gaussian distribution)

Conditional density: no_water | turno
Coefficients:
(Intercept)        turno  
  5.7588547   -0.8451049  
Standard deviation of the residuals: 7.236526 

  Parameters of node overcrowding (Gaussian distribution)

Conditional density: overcrowding | no_water
Coefficients:
(Intercept)     no_water  
 25.0455778    0.5767251  
Standard deviation of the residuals: 8.071349 

  Parameters of node p_indig (Gaussian distribution)

Conditional density: p_indig | no_water + overcrowding + density + lang_indig
Coefficients:
 (Intercept)      no_water  overcrowding       density    lang_indig  
 -1.13032950    0.01086504    0.02940867    0.29089930    2.84358295  
Standard deviation of the residuals: 0.4536688 

  Parameters of node density (Gaussian distribution)

Conditional density: density | no_sewage + no_electricity + overcrowding + lang_indig
Coefficients:
   (Intercept)       no_sewage  no_electricity    overcrowding      lang_indig  
   -0.66960911     -0.02684206     -0.03848719     -0.01980660     -0.90658897  
Standard deviation of the residuals: 0.6659899 

  Parameters of node lang_indig (Gaussian distribution)

Conditional density: lang_indig | no_electricity
Coefficients:
   (Intercept)  no_electricity  
    -0.3915927       0.0110290  
Standard deviation of the residuals: 0.08323559 
graphviz.plot(bnf, shape = "ellipse")
a

  k-fold cross-validation for Bayesian networks

  target learning algorithm:             Hybrid^2 Parent Children 
  number of folds:                       10 
  loss function:                         Log-Likelihood Loss (Gauss.) 
  expected loss:                         15.1827 
graphviz.plot(bnf, shape = "ellipse")

1 - mat log likelihood 14.78539
4 - mat log likelihood 15.17735

1 - lan log likelihood 14.93725
4 - lan log likelihood 14.79238
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKIyAgVGhpcyBpcyBmb3IgdGhlIGxhdGVzdCAyMDE5IFBMQU5FQSB0ZXN0IQoKYGBge3IsZWNobz1GQUxTRSx3YXJuaW5nPUZBTFNFLGluY2x1ZGU9RkFMU0V9CnBhcXVldGluZXMgPC0gYygicmVhZHhsIiwibWFncml0dHIiLCJkcGx5ciIsImdncGxvdDIiLCJ0aWR5ciIsInJlYWRyIiwic3RyaW5nciIsImZ1enp5am9pbiIsInJzdGFuIiwiREJJIiwiUlBvc3RncmVTUUwiLCJyYW5nZU1hcHBlciIsImJubGVhcm4iLCJCaW9jTWFuYWdlciIpCm5vX2luc3RhbGFkb3MgPC0gcGFxdWV0aW5lc1shKHBhcXVldGluZXMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssIlBhY2thZ2UiXSldCmlmKGxlbmd0aChub19pbnN0YWxhZG9zKSkgaW5zdGFsbC5wYWNrYWdlcyhub19pbnN0YWxhZG9zKQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJSZ3JhcGh2aXoiKQpsYXBwbHkocGFxdWV0aW5lcywgbGlicmFyeSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQpsaWJyYXJ5KFJncmFwaHZpeikKYGBgCgoKRWFzeSBnZ3Bsb3QgdGhlbWUuCmBgYHtyLGVjaG89RkFMU0Usd2FyaW5nPUZBTFNFfQp0aCA8LSB0aGVtZV9taW5pbWFsKCkrCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyYXkiKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImdyYXkyMCIpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChmYWNlPSJpdGFsaWMiLHNpemUgPSAxNCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGZhY2U9Iml0YWxpYyIsc2l6ZSA9IDE0KSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICAgICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIsCiAgICAgICAgbGVnZW5kLmJveCA9ICJ2ZXJ0aWNhbCIsCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KSwKICAgICAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3Q9MCksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsIGZhY2UgPSAiYm9sZCIsaGp1c3Q9MC4zOSkpCmBgYAoKCgpMZXQncyBkZWZpbmUgc29tZSBmdW5jdGlvbiB0byBpbnRlcmFjdCBlYXNpbHkgd2l0aCB0aGUgZGF0YWJhc2UuCmBgYHtyfQojJyBAdGl0bGUgcGxhbl9jb25uZWN0CiMnCiMnIEBkZXNjcmlwdGlvbiBUaGlzIGZ1bmN0aW9uIHRha2VzIHRoZSBQb3N0Z3JlcyBlbnZpcm9ubWVudCB2YXJpYWJsZXMKIycgdG8gYXV0b21hdGljYWxseSBjb25uZWN0IHRvIHRoZSBwbGFuZWFkYiBkYXRhYmFzZS4KIycgVGhlIGNvbm5lY3Rpb24gbXVzdCBiZSBzYXZlZCBpbnRvIGEgdmFyaWFibGUgaW4gb3JkZXIgdG8KIycgdXNlIGZ1dHVyZSBmdW5jdGlvbnMuIEknbSBjaGVhdGluZyBhbmQgbm90IHJlYWRpbmcgZnJvbSAuZW52LgojJwojJyBAZXhhbXBsZXMgY29uIDwtIHByZXZfY29ubmVjdCgpCiMnCiMnIEBwYXJhbSBObyBwYXJhbWV0ZXIgaXMgbmVlZGVkLgojJyBAZXhwb3J0CnBsYW5fY29ubmVjdCA8LSBmdW5jdGlvbigpewogIERCSTo6ZGJDb25uZWN0KFJQb3N0Z3JlU1FMOjpQb3N0Z3JlU1FMKCksCiAgaG9zdCAgICAgPSAgImxvY2FsaG9zdCIsCiAgdXNlciAgICAgPSAgInBsYW5lYSIsCiAgcGFzc3dvcmQgPSAgInBsYW5lYSIsCiAgcG9ydCAgICAgPSAgNTQzMiwKICBkYm5hbWUgICA9ICAicGxhbmVhIikKfQoKIycgQHRpdGxlIGxvYWRfcXVlcnkKIycKIycgQGRlc2NyaXB0aW9uIEFsbG93cyB0byBydW4gc3BlY2lmaWMgcXVlcmllcyB3aXRoIHRoZSBkYnJzb2NpYWwgc3ludGF4LgojJwojJyBAcGFyYW0gY29ubmVjdGlvbiBEQkkgY29ubmVjdGlvbi4gQSBjb25uZWN0aW9uIHRvIGEgZGF0YWJhc2UKIycgQHBhcmFtIHNjaGVtYSB2YXJpYWJsZS4gQSB2YWxpZCBzY2hlbWEgZnJvbSBhIGRhdGFiYXNlIG9uIHRoZQojJyBAcGFyYW0gdGhlX3RhYmxlLiAgQW4gZXhpc3RpbmcgdGFibGUgaW4gdGhlIGdpdmVuIHNjaGVtYS4KIycgQHBhcmFtIGNvbHVtcyBzdHJpbmcuIFRoZSBjb2x1bW5zIGluIHRoZSBkYXRhYmFzZSB3ZSB3YW50IHRvIHJldHJpZXZlCiMnIGluZm9ybWF0aW9uCiMnIEBwYXJhbSBvcHRpb25zIHN0cmluZy4gUGFydCBvZiB0aGUgU1FMIHF1ZXJ5IHdpdGggY29udGFpbmluZyBXSEVSRSwgT1JERVIsCiMnIExJTUlUIGFuZCBzbyBzdGF0ZW1lbnRzCiMnCiMnIEBleGFtcGxlcyBnZW9tX211bmkgPC0KIycgbG9hZF9xdWVyeShjb24scmF3LHNpZm9kZSxjb2x1bW5zPSJlbnRpZGFkZmVkZXJhdGl2YSIsb3B0aW9ucz0iV0hFUkUKIycgbWVzPSdBYnJpbCciKQojJyBAZXhwb3J0CmxvYWRfcXVlcnkgPC0gZnVuY3Rpb24oY29ubmVjdGlvbixzY2hlbWEsdGhlX3RhYmxlLGNvbHVtbnM9IioiLG9wdGlvbnM9IiIpewogICAgdGhlX3F1ZXJ5IDwtICJTRUxFQ1QgJXMgRlJPTSAlcy4lcyIKICAgIGNvbXBsZXRlIDwtIHBhc3RlMCh0aGVfcXVlcnksIiAiLG9wdGlvbnMpCiAgICBzY2hlbWEgICAgPC0gZGVwYXJzZShzdWJzdGl0dXRlKHNjaGVtYSkpCiAgICB0aGVfdGFibGUgPC0gZGVwYXJzZShzdWJzdGl0dXRlKHRoZV90YWJsZSkpCiAgICBpbml0aWFsIDwtIFJQb3N0Z3JlU1FMOjpkYlNlbmRRdWVyeShjb25uZWN0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwcmludGYoY29tcGxldGUsY29sdW1ucyxzY2hlbWEsdGhlX3RhYmxlKSkKICAgIHJldHVybihpbml0aWFsKQp9CgojJyBAdGl0bGUgbG9hZF90YWJsZQojJwojJyBAZGVzY3JpcHRpb24gVGhpcyBmdW5jdGlvbiBsb2FkcyBhIGNvbm5lY3Rpb24gdG8KIycgYSBnaXZlbiB0YWJsZSBmcm9tIGEgcGFydGljdWxhciBzY2hlbWEgaW4gYSBkYXRhYmFzZQojJyBjb25uZWN0aW9uLgojJwojJyBAcGFyYW0gY29ubmVjdGlvbiBEQkkgY29ubmVjdGlvbi4gQSBjb25uZWN0aW9uIHRvIGEgZGF0YWJhc2UKIycgbXVzdCBiZSBvcGVuIGFuZCBnaXZlbi4KIycgQHBhcmFtIHNjaGVtYSB2YXJpYWJsZS4gQSB2YWxpZCBzY2hlbWEgZnJvbSBhIGRhdGFiYXNlIG9uIHRoZQojJyBjb25uZWN0ZWQgZGF0YWJhc2UuCiMnIEBwYXJhbSB0aGVfdGFibGUuIEFuIGV4aXN0aW5nIHRhYmxlIGluIHRoZSBnaXZlbiBzY2hlbWEuCiMnCiMnIEBleGFtcGxlcyB0aGVfZGljPC1sb2FkX3RhYmxlKGNvbixyYXcsc2lmb2RlX2RpYykKIycgQGV4cG9ydApsb2FkX3RhYmxlIDwtIGZ1bmN0aW9uKGNvbm5lY3Rpb24sc2NoZW1hLHRoZV90YWJsZSl7CiAgICB0aGVfcXVlcnkgPC0gIlNFTEVDVCAqIEZST00gJXMuJXMiCiAgICBzY2hlbWEgICAgPC0gZGVwYXJzZShzdWJzdGl0dXRlKHNjaGVtYSkpCiAgICB0aGVfdGFibGUgPC0gZGVwYXJzZShzdWJzdGl0dXRlKHRoZV90YWJsZSkpCiAgICBpbml0aWFsIDwtIFJQb3N0Z3JlU1FMOjpkYlNlbmRRdWVyeShjb25uZWN0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwcmludGYodGhlX3F1ZXJ5LHNjaGVtYSx0aGVfdGFibGUpKQp9CgojJyBAdGl0bGUgbGFyZ2VfdGFibGUKIycKIycgQGRlc2NyaXB0aW9uIFRoaXMgZnVuY3Rpb24gbG9hZHMgYSBjb25uZWN0aW9uIHRvIGEgbGFyZ2UgdGFibGUgd2l0aG91dAojJyBsb2FkaW5nIGl0IHRvIG1lbW9yeS4KIycKIycgQHBhcmFtIGNvbm5lY3Rpb24gREJJIGNvbm5lY3Rpb24uIEEgY29ubmVjdGlvbiB0byBhIGRhdGFiYXNlCiMnIG11c3QgYmUgb3BlbiBhbmQgZ2l2ZW4uCiMnIEBwYXJhbSBzY2hlbWEgdmFyaWFibGUuIEEgdmFsaWQgc2NoZW1hIGZyb20gYSBkYXRhYmFzZSBvbiB0aGUKIycgY29ubmVjdGVkIGRhdGFiYXNlLgojJyBAcGFyYW0gdGhlX3RhYmxlLiBBbiBleGlzdGluZyB0YWJsZSBpbiB0aGUgZ2l2ZW4gc2NoZW1hLgojJwojJyBAZXhhbXBsZXMgY3Vpc190YWJsZSA8LSBsYXJnZV90YWJsZShjb24scmF3LGN1aXNfMzlfOSkKIycgQGV4cG9ydApsYXJnZV90YWJsZSA8LSBmdW5jdGlvbihjb25uZWN0aW9uLHNjaGVtYSx0aGVfdGFibGUpewogICAgc2NoZW1hICAgIDwtIGRlcGFyc2Uoc3Vic3RpdHV0ZShzY2hlbWEpKQogICAgdGhlX3RhYmxlIDwtIGRlcGFyc2Uoc3Vic3RpdHV0ZSh0aGVfdGFibGUpKQogICAgcmV0cmlldmVkIDwtIGRwbHlyOjp0YmwoY29ubmVjdGlvbixkYnBseXI6OmluX3NjaGVtYShzY2hlbWEsdGhlX3RhYmxlKSkKICAgIHJldHVybihyZXRyaWV2ZWQpCn0KCiMnIEB0aXRsZSBjbGVhcl9yZXN1bHRzCiMnCiMnIEBkZXNjcmlwdGlvbiBUaGlzIGZ1bmN0aW9uIGNsZWFycyB0aGUgcmVzdWx0cyBmcm9tIGEgcHJldmlvdXMgZXhlY3V0ZWQKIycgcXVlcnkuCiMnCiMnIEBwYXJhbSBjb25uZWN0aW9uIERCSSBjb25uZWN0aW9uLiBBIGNvbm5lY3Rpb24gdG8gYSBkYXRhYmFzZSBtdXN0IGJlIG9wZW4gYW5kIGdpdmVuLgojJwojJyBAZXhhbXBsZXMgY2xlYXJfcmVzdWx0cyhjb24pCiMnIEBleHBvcnQKY2xlYXJfcmVzdWx0cyA8LSBmdW5jdGlvbihjb25uZWN0aW9uKXsKICAgIERCSTo6ZGJDbGVhclJlc3VsdChEQkk6OmRiTGlzdFJlc3VsdHMoY29ubmVjdGlvbilbWzFdXSkKfQoKIycgQHRpdGxlIGpvaW5fdGFibGVzCiMnCiMnIEBkZXNjcmlwdGlvbiBSZXR1cm5zIGEgbWF0Y2ggYmV0d2VlbiB0d28gdGJsIGJ5IGRlZmluZWQga2V5CiMnIEBwYXJhbSBsZWZ0X3RhYmxlIGEgdGJsLWxpa2Ugb2JqZWN0CiMnIEBwYXJhbSByaWdodF90YWJsZSBhIHRibC1saWtlIG9iamVjdAojJyBAcGFyYW0gbGVmdF9rZXkgdGhlIGNvbHVtbiBuYW1lIGZyb20gbGVmdF90YWJsZSB0byBjb21wYXJlCiMnIEBwYXJhbSByaWdodF9rZXkgdGhlIGNvbHVtbiBuYW1lIGZyb20gcmlnaHRfdGFibGUgdG8gY29tcGFyZQojJwojJyBAZXhhbXBsZXMgam9pbl90YWJsZXMoY3Vpc19zYW1wbGUsbGxhdmVfaG9nYXJfaCxkb21pY2lsaW9zX3NhbXBsZV9xdWVyeSxsbGF2ZV9ob2dhcl9oKQojJyBAZXhwb3J0CmpvaW5fdGFibGVzIDwtIGZ1bmN0aW9uKGxlZnRfdGFibGUsIGxlZnRfa2V5LCByaWdodF90YWJsZSwgcmlnaHRfa2V5KXsKICAgIGxlZnRfa2V5IDwtIHN1YnN0aXR1dGUobGVmdF9rZXkpCiAgICByaWdodF9rZXkgPC0gZGVwYXJzZShzdWJzdGl0dXRlKHJpZ2h0X2tleSkpCiAgICBpbl90YWJsZXMgPC0gbGVmdF90YWJsZSAlPiUKICAgICAgICBkcGx5cjo6ZmlsdGVyKGxlZnRfa2V5ICVpbiUgcmlnaHRfdGFibGVbW3JpZ2h0X2tleV1dKQogICAgcmV0dXJuKGluX3RhYmxlcykKfQoKIycgQHRpdGxlIHJldHJpZXZlX3Jlc3VsdAojJwojJyBAZGVzY3JpcHRpb24gUmV0dXJuIHRoZSBmZXRjaCByZXN1bHRzIG9mIGEgcXVlcnkKIycgQHBhcmFtIHF1ZXJ5IEFuIGV4ZWMgdW5mZXRjaGVkIHF1ZXJ5CiMnCiMnIEBleGFtcGxlcyBzYW1wbGVfdGFibGUobG9hZF90YWJsZShwcmV2X2Nvbm5lY3QoKSxyYXcsc2lmb2RlKSkKIycgQGV4cG9ydApyZXRyaWV2ZV9yZXN1bHQgPC0gZnVuY3Rpb24ocXVlcnksbj0tMSxudW1iZXI9SW5mKXsKICAgIGlmIChjbGFzcyhxdWVyeSlbMV0gPT0gInRibF9kYmkiKXsKICAgICAgICB0aGVfdGFibGUgPC0gZHBseXI6OmNvbGxlY3QocXVlcnksbj1udW1iZXIpCiAgICAgICAgcmV0dXJuKHRoZV90YWJsZSkKICAgIH0KICAgIGVsc2V7CiAgICB0aGVfdGFibGUgPC0gREJJOjpkYkZldGNoKHF1ZXJ5LG4pCiAgICByZXR1cm4odGhlX3RhYmxlKQogICAgfQp9CgojJyBAdGl0bGUgbG9hZF9nZW9tCiMnCiMnIEBkZXNjcmlwdGlvbiBHaXZlcyBhICJyZWFkeSB0byBnbyIgZGF0YSBmcmFtZSBmb3IgZ2VvbWV0cnkgcGxvdHRpbmcKIycKIycgQHBhcmFtIGNvbm5lY3Rpb24gREJJIGNvbm5lY3Rpb24uIEEgY29ubmVjdGlvbiB0byBhIGRhdGFiYXNlCiMnIEBwYXJhbSBzY2hlbWEgdmFyaWFibGUuIEEgdmFsaWQgc2NoZW1hIGZyb20gYSBkYXRhYmFzZSBvbiB0aGUKIycgQHBhcmFtIHRoZV90YWJsZS4gIEFuIGV4aXN0aW5nIHRhYmxlIGluIHRoZSBnaXZlbiBzY2hlbWEuCiMnIEBwYXJhbSBjb2x1bXMgc3RyaW5nLiBUaGUgY29sdW1ucyBpbiB0aGUgZGF0YWJhc2Ugd2Ugd2FudCB0byByZXRyaWV2ZS4KIycgRGVmYXVsdHMgY3ZlX2VudCwgY3ZlX211biwgY3ZlX211bmkuCiMnIEBwYXJhbSBnZW9tX2NvbCAuIFRoZSBuYW1lIG9mIHRoZSBjb2x1bW4gaW4gdGhlIGRhdGFiYXNlIHRoYXQgY29udGFpbnMgYSBnZW9tZXRyeQojJyBAcGFyYW0gY29sX3NoYXBlLiBUaGUgbmFtZSBvZiB0aGUgY29sdW1uIHRoYXQgd2Ugd2FudCB0byB1c2UgdG8gam9pbgojJyBpbmZvcm1hdGlvbgojJyBAcGFyYW0gb3B0aW9ucyBzdHJpbmcuIFBhcnQgb2YgdGhlIFNRTCBxdWVyeSB3aXRoIGNvbnRhaW5pbmcgV0hFUkUsIE9SREVSLAojJyBMSU1JVCBhbmQgc28gc3RhdGVtZW50cwojJwojJyBAZXhhbXBsZXMgZ2VvbV9tdW5pIDwtIGxvYWRfZ2VvbShjb24xLHJhdyxnZW9tX211bmljaXBpb3MsZ2VvbV9jb2w9Z2VvbSxjb2xfc2hhcGU9Y3ZlX211bmksb3B0aW9ucz1vcHRpb25zKQojJyBAZXhwb3J0CmxvYWRfZ2VvbSA8LSBmdW5jdGlvbihjb25uZWN0aW9uLHNjaGVtYSx0aGVfdGFibGUsY29sdW1ucz0iXCJDVkVHRU9cIiwgXCJDVkVfRU5UXCIsIFwiQ1ZFX01VTlwiIiwgZ2VvbV9jb2wsIGNvbF9zaGFwZSwgb3B0aW9ucz0iIil7CiAgICBnZW9tX2NvbCA8LSBkZXBhcnNlKHN1YnN0aXR1dGUoZ2VvbV9jb2wpKQogICAgc2NoZW1hICAgIDwtIGRlcGFyc2Uoc3Vic3RpdHV0ZShzY2hlbWEpKQogICAgdGhlX3RhYmxlIDwtIGRlcGFyc2Uoc3Vic3RpdHV0ZSh0aGVfdGFibGUpKQogICAgY29sX3NoYXBlIDwtIGRlcGFyc2Uoc3Vic3RpdHV0ZShjb2xfc2hhcGUpKQogICAgZ2VvbV9jb2wyIDwtIHBhc3RlMCgiXCIiLGdlb21fY29sLCJcIiIpCgogICAgdGhlX3F1ZXJ5IDwtICJTRUxFQ1QgJXMgRlJPTSAlcy4lcyIKICAgIGdlb21fY29sX2FzIDwtIHNwcmludGYoIiwgJXMgYXMgZ2VvbSIsZ2VvbV9jb2wyKQogICAgY29sdW1ucyA8LSBwYXN0ZTAoY29sdW1ucyxnZW9tX2NvbF9hcykKICAgIGNvbXBsZXRlIDwtIHBhc3RlMCh0aGVfcXVlcnksIiAiLG9wdGlvbnMpCgogICAgaW5pdGlhbCA8LSBSUG9zdGdyZVNRTDo6ZGJTZW5kUXVlcnkoY29ubmVjdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcHJpbnRmKGNvbXBsZXRlLGNvbHVtbnMsc2NoZW1hLHRoZV90YWJsZSkpICU+JQogICAgcmV0cmlldmVfcmVzdWx0KCkKCiAgICBtdW5fc2hwID0gcmFuZ2VNYXBwZXI6OldLVDJTcGF0aWFsUG9seWdvbnNEYXRhRnJhbWUoaW5pdGlhbCwgZ2VvbSA9ICJnZW9tIiwgaWQgPSBjb2xfc2hhcGUpCiAgICBtdW5fZGYgPC0gZm9ydGlmeShtdW5fc2hwLCByZWdpb24gPSBjb2xfc2hhcGUpCiAgICBuYW1lcyhtdW5fZGYpW25hbWVzKG11bl9kZik9PSJpZCJdIDwtIGNvbF9zaGFwZQoKICAgIHJldHVybihtdW5fZGYpCn0KCiMnIEB0aXRsZSBkaXNjb25fZGIKIycKIycgQGRlc2NyaXB0aW9uIFRoaXMgZnVuY3Rpb24gZGlzY29ubmVjdHMgYSBQb3N0Z3JlU1FMCiMnIGNvbm5lY3Rpb24uCiMnCiMnIEBwYXJhbSBjb25uZWN0aW9uIERCSSBjb25uZWN0aW9uLiBBIGNvbm5lY3Rpb24gdG8gYSBkYXRhYmFzZSBtdXN0IGJlIG9wZW4gYW5kIGdpdmVuLgojJwojJyBAZXhhbXBsZXMgZGlzY29uX2RiKGNvbikKIycgQGV4cG9ydApkaXNjb25fZGIgPC0gZnVuY3Rpb24oY29ubmVjdGlvbil7CiAgICBSUG9zdGdyZVNRTDo6ZGJEaXNjb25uZWN0KGNvbm5lY3Rpb24pCn0KYGBgCgoKRmlyc3Qgd2UgbG9hZCB0aGUgUExBTkVBIHJlc3VsdHMuCmBgYHtyfQojIGRpc2Nvbl9kYihjb24pCmNvbiA8LSBwbGFuX2Nvbm5lY3QoKQoKcXVlcnkgPC0gbG9hZF90YWJsZShjb24scHVibGljLHBsYW5lYSkKcGxhbmVhIDwtIHF1ZXJ5ICU+JSByZXRyaWV2ZV9yZXN1bHQoKQpjbGVhcl9yZXN1bHRzKGNvbikKYGBgCgpXZSBsb2FkIHRoZSBnZW9tZXRyaWVzIGJ1dCB3aWxsIHVzZSB0aGVtIGxhdGVyCmBgYHtyfQpnZW9tX211bmkgPC0gbG9hZF9nZW9tKGNvbixwdWJsaWMsZ2VvbV9tdW5pLGdlb21fY29sPVdLVCxjb2xfc2hhcGU9Q1ZFR0VPKQpgYGAKCgpgYGB7cn0KdGhlX25hbWVzIDwtIGNvbG5hbWVzKHBsYW5lYSkKbG9ncm9zIDwtIHRoZV9uYW1lc1sxOTpsZW5ndGgoKHRoZV9uYW1lcykpXQpwbGFuZWFbbG9ncm9zXSA8LSBzYXBwbHkocGxhbmVhW2xvZ3Jvc10sYXMubnVtZXJpYykKcGxhbmVhJG11bmljaXBpbyA8LSBwbGFuZWEkbXVuaWNpcGlvICU+JSBpY29udihmcm9tPSJVVEYtOCIsdG89IkFTQ0lJLy9UUkFOU0xJVCIpICU+JSB0b3VwcGVyKCkKYGBgCgoKV2UgbmVlZCB0byBjaGFuZ2Ugc29tZSBtdW5pY2lwYWxpdGllcyBuYW1lcyBpbiBvcmRlciB0byBqb2luIHRoaXMgZGF0YWJhc2Ugd2l0aCB0aGUgQ09OQVBPIG9uZS4KVGhpcyBhcmUgdGhlIG1pc3NlZC1tYXRjaGVkIG5hbWVzIGluIHRoZSB0YWJsZXMgb2YgdGhlIGRhdGFiYXNlLiBJJ2xsIGNoYW5nZSB0aGUgb25lcyBmcm9tIHRoZSBzY2hvb2xzLgpgYGB7cn0KZnJvbV9zY2hvb2xzIDwtIGMoIkNELiBERUwgQ0FSTUVOIiwiRE9DVE9SIEJFTElTQVJJTyBET01JTkdVRVoiLCJURU1PU0FDSEkiLCJQRUpQWU9OIEJMQU5DTyIsIkRPTE9SRVMgSElEQUxHTyIsIlNJTEFPIiwiWkFQT1RMQU4gRUwgR1JBTkRFIChDSVVEQUQgR1VaTUFOKSIsIkNVQVVUSVRMQU4gKERFIEdBUkNJQSBCQVJSQUdBTikiLCJTQU4gTUFSVElOIERFIEJPTEEmT1MiLCJDQSZBREFTIERFIE9CUkVHT04iLCJTQU4gSk9TRT8gREVMIFJJTkNPTiIsIlZBTExFIERFIENIQUxDTyBTT0xJREFSSURBIiwiU0FOIE1BUlRJTiBERSBMQVMgUElSQU1JREUiLCJIRVJPSUNBIFpJVEFDVUFSTyIsIlRBTkhVQVRPIERFIEdVRVJSRVJPIiwiQVJJTyBERSBST1NBTEVTIiwiVFVNQklTQ0FUSU8gREUgUlVJWiIsIkpVTkdBUEVPIERFIEpVQVJFWiIsIkFDVUlUWklPIERFTCBDQU5KRSIsIlNVU1VQVUFUTyBERSBHVUVSUkVSTyIsIkdBQlJJRUwgWkFNT1JBIChMT01CQVJESUEpIiwiQkVOSVRPIEpVQVJFWiAoTEFVUkVMRVMpIiwiSFVFVEFNTyBERSBOVU5FWiIsIlRJUVVJQ0hFTyIsIlRMQUxQVUpBSFVBIERFIFJBWU9OIiwiUEFSQUNITyBERSBWRVJEVVpDTyIsIkRSLiBCRVNMSVNBUklPIERPTUlOR1VFWiIsIlRMQUxUSVpBUEFOIiwiRUwgTkFZQVIiLCJTQU4gTUFURU8gWVVDVVRJTkRPIiwiRUwgUk9TQVJJTyIsIkFUTFRaQVlBTkNBIiwiTUVERUxMSU4gREUgQlJBVk8iLCJOQU5DSElUQUwgREUgTEFaQVJPIENBUkRFTkFTIERFTCBSSSIpCgp0b19jb25hcG8gPC0gYygiQ0FSTUVOIiwiRFIuIEJFTElTQVJJTyBET01JTkdVRVoiLCJURU1PU0FDSElDIiwiUEVOT04gQkxBTkNPIiwiRE9MT1JFUyBISURBTEdPIENVTkEgREUgTEEgSU5ERVBFTkRFTkNJQSBOQUNJT05BTCIsIlNJTEFPIERFIExBIFZJQ1RPUklBIiwiWkFQT1RMQU4gRUwgR1JBTkRFIiwiQ1VBVVRJVExBTiBERSBHQVJDSUEgQkFSUkFHQU4iLCJTQU4gTUFSVElOIERFIEJPTEFOT1MiLCJDQU5BREFTIERFIE9CUkVHT04iLCJTQU4gSk9TRSBERUwgUklOQ09OIiwiVkFMTEUgREUgQ0hBTENPIFNPTElEQVJJREFEIiwiU0FOIE1BUlRJTiBERSBMQVMgUElSQU1JREVTIiwiWklUQUNVQVJPIiwiVEFOSFVBVE8iLCJBUklPIiwiVFVNQklTQ0FUSU8iLCJKVU5HQVBFTyIsIkFDVUlUWklPIiwiU1VTVVBVQVRPIiwiR0FCUklFTCBaQU1PUkEiLCJKVUFSRVoiLCJIVUVUQU1PIiwiVElRVUlDSEVPIERFIE5JQ09MQVMgUk9NRVJPIiwiVExBTFBVSkFIVUEiLCJQQVJBQ0hPIiwiRFIuIEJFTElTQVJJTyBET01JTkdVRVoiLCJUTEFMVElaQVBBTiBERSBaQVBBVEEiLCJERUwgTkFZQVIiLCJTQU4gTUFURU8gWVVDVVRJTkRPTyIsIlJPU0FSSU8iLCJBTFRaQVlBTkNBIiwiTUVERUxMSU4iLCJOQU5DSElUQUwgREUgTEFaQVJPIENBUkRFTkFTIERFTCBSSU8iKQoKcGxhbmVhJG11bmljaXBpbyA8LSBwbHlyOjptYXB2YWx1ZXMocGxhbmVhJG11bmljaXBpbywgdG89dG9fY29uYXBvLCBmcm9tPWZyb21fc2Nob29scykKYGBgCgpMb2FkIHRoZSBDT05BUE8gdGFibGUKYGBge3J9CnRoZV9vcHRpb25zIDwtICJXSEVSRSBcIkFOT1wiID0gMjAxNSIKcXVlcnkgPC0gbG9hZF9xdWVyeShjb24scHVibGljLGNvbmFwbyxvcHRpb25zPXRoZV9vcHRpb25zKQpjb25hcG8gPC0gcXVlcnkgJT4lIHJldHJpZXZlX3Jlc3VsdCgpCmNsZWFyX3Jlc3VsdHMoY29uKQpjb25hcG8kQ0xBVkUgPC0gY29uYXBvJENMQVZFICU+JSBzdHJfcGFkKHdpZHRoPTUscGFkPSIwIikKY29uYXBvJE5PTV9FTlQgJTw+JSBzdHJfcmVwbGFjZV9hbGwoIk9heGFjYS4qIiwiT2F4YWNhIikKY29uYXBvJE1VTiA8LSBjb25hcG8kTVVOICU+JSBpY29udihmcm9tPSJVVEYtOCIsdG89IkFTQ0lJLy9UUkFOU0xJVCIpICU+JSB0b3VwcGVyKCkgJT4lIHN0cl9yZXBsYWNlX2FsbCgiU0FOIFBFRFJPIE1JWFRFUEVDLioiLCJTQU4gUEVEUk8gTUlYVEVQRUMiKQpjb25hcG8gJTw+JSBzZWxlY3QoQ0xBVkUsTk9NX0VOVCxNVU4sU0VYTyxQT0IpICU8PiUgZHBseXI6Omdyb3VwX2J5KENMQVZFLE5PTV9FTlQsTVVOKSAlPiUgZHBseXI6OnN1bW1hcmlzZShQT1A9c3VtKFBPQikpCmBgYAoKTG9hZCB0aGUgbWFyZ2luYWxpemF0aW9uIHRhYmxlIGFuZCBjaGFuZ2UgdGhlIHZhcmlhYmxlIG5hbWVzIHRvIHVzYWJsZSBvbmVzLgpgYGB7cn0KdGhlX29wdGlvbnMgPC0gIldIRVJFIFwiWUVBUlwiID0gMjAxNSIKcXVlcnkgPC0gbG9hZF9xdWVyeShjb24scHVibGljLG1hcmdpbmFsaXphdGlvbixvcHRpb25zPXRoZV9vcHRpb25zKQptYXJnaW5hY2lvbiA8LSBxdWVyeSAlPiUgcmV0cmlldmVfcmVzdWx0KCkKY2xlYXJfcmVzdWx0cyhjb24pCgojIFRPRE8gY2hhbmdlIHRoaXMgdG8gU1FMIHF1ZXJ5Cm1hcmdpbmFjaW9uICU8PiUgc2VsZWN0KC1DVkVfRU5ULC1FTlQsLU1VTiwtVlApCmNvbG5hbWVzKG1hcmdpbmFjaW9uKSA8LSBjKCJDTEFWRSIsInBvcF9tYXJnIiwiaWxsaXRlcmFjeSIsImVsZW1lbnRhcnkiLCJub19zZXdhZ2UiLCJub19lbGVjdHJpY2l0eSIsIm5vX3dhdGVyIiwib3ZlcmNyb3dkaW5nIiwiZGlydF9mbG9vciIsImxlc3NfNWsiLCJsZXNzXzJtaW53YWdlIiwib3ZzZCIsIm92c2RzZSIsImltIiwiZ20iLCJpbmQwYTEwMCIsImx1Z2FyIiwibHVnYXJlc3QiLCJZRUFSIikKCiMgVE9ETyBzZXQgQ0xBVkUgYXMgVEVYVCBpbiBtYXJnaW5hbGl6YXRpb24gc2NoZW1hIGZvciBTUUwgaW5nZXN0IGluIHBpcGVsaW5lCm1hcmdpbmFjaW9uJENMQVZFIDwtIGFzLmNoYXJhY3RlcihtYXJnaW5hY2lvbiRDTEFWRSkgJT4lIHN0cl9wYWQod2lkdGg9NSxwYWQ9IjAiKQpjb2wybnVtIDwtIGNvbG5hbWVzKG1hcmdpbmFjaW9uICU+JSBzZWxlY3QoLUNMQVZFLC1nbSkpCm1hcmdpbmFjaW9uW2NvbDJudW1dIDwtIHNhcHBseShtYXJnaW5hY2lvbltjb2wybnVtXSxhcy5udW1lcmljKQoKbWFyZ2luYWNpb24gPC0gbWFyZ2luYWNpb25bIWlzLm5hKG1hcmdpbmFjaW9uJGdtKSxdCmBgYAoKCkpvaW4gdGhlIHRocmVlIHRhYmxlcy4gSSBzaG91bGQgcnVuIHRoaXMgaW4gUG9zdGdyZXMuICpUT0RPKiB3cml0ZSBTUUwgc3RhdGVtZW50LgpgYGB7cn0Kc2Nob29scyA8LSBjb25hcG8gJT4lIHJpZ2h0X2pvaW4ocGxhbmVhLGJ5PWMoIk5PTV9FTlQiPSJlbnRpZGFkIiwiTVVOIj0ibXVuaWNpcGlvIikpICU+JSBsZWZ0X2pvaW4obWFyZ2luYWNpb24sIGJ5PSJDTEFWRSIpICU+JSB1bmdyb3VwKE5PTV9FTlQpCnNjaG9vbHMkZ20gPC0gZmFjdG9yKHNjaG9vbHMkZ20sbGV2ZWxzPWMoIk11eSBiYWpvIiwiQmFqbyIsIk1lZGlvIiwiQWx0byIsIk11eSBhbHRvIikpCnNjaG9vbHMkdGlwb19lc2N1ZWxhIDwtIGZhY3RvcihzY2hvb2xzJHRpcG9fZXNjdWVsYSxsZXZlbHM9YygiR2VuZXJhbCBQw7pibGljYSIsIlTDqWNuaWNhIFDDumJsaWNhIiwiVGVsZXNlY3VuZGFyaWEiLCJQcml2YWRhIiwiQ29tdW5pdGFyaWEiKSkKYGBgCgpUaGlzIHJlc3VsdCBpcyBmb3IgYSB0YWJsZSBpbiB0aGUgZG9jdW1lbnQKYGBge3J9Cm1hcmdpbmFjaW9uICU+JSBncm91cF9ieShnbSkgJT4lIHN1bW1hcmlzZShuKCkpCmBgYAoKSG93IG1hbnkgc2Nob29scwpgYGB7cn0KbGVuZ3RoKHVuaXF1ZShwbGFuZWEkY2xhdmUpKQpzY2hvb2xzICU+JSBncm91cF9ieShnbSkgJT4lIHN1bW1hcmlzZShuKCkpCmBgYAoKYGBge3J9CnNjaG9vbHNbIWlzLm5hKHNjaG9vbHMkSVZfcG9yY19sZW4pICYgIWlzLm5hKHNjaG9vbHMkSVZfcG9yY19tYXQpLF0gJT4lICBzdW1tYXJpc2UobWVhbihJVl9wb3JjX2xlbiksbWVhbihJVl9wb3JjX21hdCkpCmBgYAoKVGhlIG51bWJlciBvZiBzY2hvb2xzIGluIGEgbXVuaWNpcGFsaXR5LgpgYGB7cn0Kc2Nob29sc19ieV9tdW4gPC0gcGxhbmVhICU+JSBkaXN0aW5jdChlbnQsbXVuaWNpcGlvLGVzY3VlbGEsbWFyZ2luYWNpb24pICU+JSBncm91cF9ieShlbnQsbXVuaWNpcGlvKSAlPiUgc3VtbWFyaXNlKG51bWJlcj1uKCkpCm51bV9zY2hvb2xzIDwtIHNjaG9vbHNfYnlfbXVuICU+JSBncm91cF9ieShudW1iZXIpICU+JSBzdW1tYXJpc2UobnVtX3NjaG9vbCA9IG4oKSkKYGBgCgpgYGB7cn0Kc2Nob29scyRDTEFWRSAlPiUgdW5pcXVlKCkgJT4lIGxlbmd0aCgpCmNvbmFwbyRDTEFWRSAlPiUgdW5pcXVlKCkgJT4lIGxlbmd0aCgpCmBgYAoKRml0IHBvcndlci1sYXcgdG8gbnVtYmVyIG9mIHNjaG9vbHMKYGBge3J9CnNjaG9vbHNfcG93ZXJfbGF3IDwtIGxtKGxvZzEwKG51bV9zY2hvb2wpIH4gbG9nMTAobnVtYmVyKSxudW1fc2Nob29scykKYGBgCgoKYGBge3J9CnNlcV9zY2hvb2xzIDwtIHNlcShtaW4obnVtX3NjaG9vbHMpLG1heChudW1fc2Nob29scykpCmFkanVzdGVkX3Bvd2VyIDwtIDEwXnNjaG9vbHNfcG93ZXJfbGF3JGNvZWZmaWNpZW50c1sxXSpzZXFfc2Nob29sc15zY2hvb2xzX3Bvd2VyX2xhdyRjb2VmZmljaWVudHNbMl0Kc2Nob29sc19wb3dlcl9sYXcKYGBgCgoKYGBge3J9CnRoZV9wX2xhdyA8LSB0aWJibGUobnVtYmVyPXNlcV9zY2hvb2xzLHZhbHVlPWFkanVzdGVkX3Bvd2VyKQpudW1fc2Nob29scyAlPD4lIGxlZnRfam9pbih0aGVfcF9sYXcsYnk9Im51bWJlciIpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QobnVtX3NjaG9vbHMpKwogIGdlb21fYmFyKGFlcyh4PW51bWJlcix5PW51bV9zY2hvb2wpLHN0YXQgPSAiaWRlbnRpdHkiKSsKICBnZW9tX2xpbmUoYWVzKHg9bnVtYmVyLHk9dmFsdWUpLGNvbG9yPSJyZWQiKSsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZT0iU2Nob29scyIpKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gIk11bmljaXBhbGl0aWVzIixsaW1pdHMgPSBjKDAsMjUwKSkrCiAgZ2d0aXRsZSgiTXVuaWNpcGFsaXRpZXMgYnkgbnVtYmVyIG9mIHNjaG9vbHMiKSsKICB0aAoKIyBUaGlzIHBhdGggc2hvdWxkIHBvaW50IHRvIGEgcGxhY2UgaW5zaWRlIHRoZSBkb2NrZXIgY29udGFpbmVyCmdnc2F2ZSgiLi4vLi4vZmlncy9wb3dlcmxhd19zY2hvb2xfbXVuLnBuZyIsd2lkdGggPSAxNSxoZWlnaHQgPSAxMCx1bml0cyA9ICJjbSIpCmBgYAoKYGBge3J9CmdncGxvdChudW1fc2Nob29scykrCiAgZ2VvbV9wb2ludChhZXMoeD1sb2cxMChudW1iZXIpLHk9bG9nMTAobnVtX3NjaG9vbCkpKSsKICAjIGdlb21fc21vb3RoKGFlcyh4PWxvZzEwKG51bWJlcikseT1sb2cxMChudW1fc2Nob29sKSksbWV0aG9kID0gImdsbSIpKwogIGdlb21fbGluZShhZXMoeD1sb2cxMChudW1iZXIpLHk9bG9nMTAodmFsdWUpKSxjb2xvcj0icmVkIikrCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWU9IlNjaG9vbHMiKSsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJNdW5pY2lwYWxpdGllcyIpKwogIGdndGl0bGUoIk11bmljaXBhbGl0aWVzIGJ5IG51bWJlciBvZiBzY2hvb2xzIikrCiAgdGgKCmdnc2F2ZSgiLi4vLi4vZmlncy9wb3dlcmxhd19zY2hvb2xfbXVuX2xvZy5wbmciLHdpZHRoID0gMTUsaGVpZ2h0ID0gMTAsdW5pdHMgPSAiY20iKQpgYGAKCgoKCgoKTnVtYmVyIG9mIGV2YWx1YXRlZCBzdHVkZW50cyBpbiBsYW5ndWFnZQpgYGB7cn0Kc3VtKGFzLm51bWVyaWMocGxhbmVhJGV2YWx1YWRvc19sZW4pKQpgYGAKCk51bWJlciBvZiBldmFsdWF0ZWQgc3R1ZGVudHMgaW4gbWF0aGVtYXRpY3MKYGBge3J9CnN1bShhcy5udW1lcmljKHBsYW5lYSRldmFsdWFkb3NfbWF0KSkKYGBgCgoKVGhlcmUncyBhIHZhcmlhYmxlIGNhbGxlZCByZXByZXNlbnRhdGl2aXR5IGZvciBib3RoIG1hdGggYW5kIGxhbmd1YWdlIGJ1dCBpdCdzIHVuY2xlYXIgdGhlIHBlcmNlbnRhZ2UgY3V0LW9mZiwgdGhlbiBJIHdpbGwgdXNlIHRoZSBzY2hvb2xzIHdpdGggYWJvdmUgNTAlIG9mIGJvdGggdGVzdHMKYGBge3J9CnNjaG9vbHNbKHNjaG9vbHMkcmVwcmVfbGVuID09ICJOTyIpIHwgKHNjaG9vbHMkcmVwcmVfbWF0ID09ICJOTyIpLF0KYGBgCgpgYGB7cn0Kc2Nob29sc1soc2Nob29scyRwb3JjX2xlbiA+IDUwICYgc2Nob29scyRwb3JjX21hdCA+IDUwKSxdCmBgYAoKYGBge3J9CnNjaG9vbHNbIShzY2hvb2xzJHBvcmNfbGVuID4gNTAgJiBzY2hvb2xzJHBvcmNfbWF0ID4gNTApLF0KYGBgCgpgYGB7cn0Kc2Nob29sc1soc2Nob29scyRyZXByZV9sZW4gPT0gIlPDjSIpICYgKHNjaG9vbHMkcmVwcmVfbWF0ID09ICJTw40iKSxdCnNjaG9vbHMKYGBgCgoKCgpgYGB7cn0Kc2Nob29scyA8LSBzY2hvb2xzWyhzY2hvb2xzJHBvcmNfbGVuID4gNTAgJiBzY2hvb2xzJHBvcmNfbWF0ID4gNTApLF0KbXVuX3NjaG9vbHMgPC0gc2Nob29scyAlPiUKICBzZWxlY3QoQ0xBVkUsZXNjdWVsYSxQT1AsbWFyZ2luYWNpb24sZ20sZWxlbWVudGFyeSxpbGxpdGVyYWN5KSAlPiUKICB1bmlxdWUoKSAlPiUgZHBseXI6Omdyb3VwX2J5KENMQVZFKSAlPiUKICBkcGx5cjo6c3VtbWFyaXNlKG51bV9zY2hvb2wgPSBuKCksUE9CPXVuaXF1ZShQT1ApLGVsZW1lbnRhcnk9dW5pcXVlKGVsZW1lbnRhcnkpLGlsbGl0ZXJhY3k9dW5pcXVlKGlsbGl0ZXJhY3kpLGdtPXVuaXF1ZShnbSkpICU+JQogIG11dGF0ZShzcGMgPSBudW1fc2Nob29sLyhQT0IvMTAwMDAwKSkKCmBgYAoKTnVtYmVyIG9mIGV2YWx1YXRlZCBzdHVkZW50cyBpbiBsYW5ndWFnZQpgYGB7cn0Kc3VtKGFzLm51bWVyaWMoc2Nob29scyRldmFsdWFkb3NfbGVuKSkKYGBgCgpOdW1iZXIgb2YgZXZhbHVhdGVkIHN0dWRlbnRzIGluIG1hdGhlbWF0aWNzCmBgYHtyfQpzdW0oYXMubnVtZXJpYyhzY2hvb2xzJGV2YWx1YWRvc19tYXQpKQpgYGAKCmBgYHtyfQp1bmlxdWUoKHNjaG9vbHMkY2xhdmUpKSAlPiUgbGVuZ3RoKCkKYGBgCgpgYGB7cn0KdW5pcXVlKChzY2hvb2xzJENMQVZFKSkgJT4lIGxlbmd0aCgpCmBgYAoKCgpgYGB7cn0KbWFyX3Njb3JlcyA8LSBzY2hvb2xzICU+JSBzZWxlY3QoQ0xBVkUsUE9QLGlsbGl0ZXJhY3ksZWxlbWVudGFyeSxJX3BvcmNfbGVuLElJX3BvcmNfbGVuLElJSV9wb3JjX2xlbixJVl9wb3JjX2xlbixJX3BvcmNfbWF0LElJX3BvcmNfbWF0LElJSV9wb3JjX21hdCxJVl9wb3JjX21hdCkKbWFyX3Njb3JlcyAlPD4lIGdyb3VwX2J5KENMQVZFKSAlPiUgZHBseXI6OnN1bW1hcmlzZShudW1fc2Nob29sPW4oKSxQT1A9dW5pcXVlKFBPUCksaWxsaXRlcmFjeT11bmlxdWUoaWxsaXRlcmFjeSksZWxlbWVudGFyeT11bmlxdWUoZWxlbWVudGFyeSksSV9sZW4gPSBtZWFuKElfcG9yY19sZW4pLElJX2xlbiA9IG1lYW4oSUlfcG9yY19sZW4pLElJSV9sZW4gPSBtZWFuKElJSV9wb3JjX2xlbiksSVZfbGVuID0gbWVhbihJVl9wb3JjX2xlbiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJX21hdCA9IG1lYW4oSV9wb3JjX21hdCksSUlfbWF0ID0gbWVhbihJSV9wb3JjX21hdCksSUlJX21hdCA9IG1lYW4oSUlJX3BvcmNfbWF0KSxJVl9tYXQgPSBtZWFuKElWX3BvcmNfbWF0KSkKYGBgCgoKYGBge3J9CmdncGxvdChtdW5fc2Nob29scykrCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHNwYyksYmlud2lkdGggPSAxMCxmaWxsPSIjODQ4MjU2IikrCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWU9IlNjaG9vbHMgcGVyIDEwMCwwMDAgcG9wIikrCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWU9Ik11bmljaXBhbGl0aWVzIikrCiAgdGgKYGBgCgoKClRyYW5zbGF0ZSBzb21lIHZhcmlhYmxlcyB0byBlbmdsaXNoLiBUaGVyZSBhcmUgc29tZSB3aXRoIHBlY3VsaWFyIG5hbWUgdGhhdCBzaG91bGRuJ3QgYmUgdHJhbnNsYXRlZC4KYGBge3J9CnNjaG9vbHNfdGlkeSA8LSBzY2hvb2xzICU+JSBzZWxlY3QodGlwb19lc2N1ZWxhLGdtLElfcG9yY19sZW4sSUlfcG9yY19sZW4sSUlJX3BvcmNfbGVuLElWX3BvcmNfbGVuLElfcG9yY19tYXQsSUlfcG9yY19tYXQsSUlJX3BvcmNfbWF0LElWX3BvcmNfbWF0KQpzY2hvb2xzX3RpZHkgJTw+JSBwaXZvdF9sb25nZXIoYyhJX3BvcmNfbGVuLElJX3BvcmNfbGVuLElJSV9wb3JjX2xlbixJVl9wb3JjX2xlbixJX3BvcmNfbWF0LElJX3BvcmNfbWF0LElJSV9wb3JjX21hdCxJVl9wb3JjX21hdCkpCgp1bmlxdWUoc2Nob29scyR0aXBvX2VzY3VlbGEpCmVuZ2xpc2hUeXBlIDwtIGMoIkNvbXVuaXRhcmlhIiwiUHVibGljIEcuIiwgIlByaXZhdGUiLCAiUHVibGljIFQuIiwgIlRlbGVzZWN1bmRhcmlhIikKc3BhbmlzaFR5cGUgPC0gYygiQ29tdW5pdGFyaWEiLCJHZW5lcmFsIFDDumJsaWNhIiwgIlByaXZhZGEiLCAiVMOpY25pY2EgUMO6YmxpY2EiLCAiVGVsZXNlY3VuZGFyaWEiKQoKZW5nbGlzaE0gPC0gYygiVmVyeSBMb3ciLCJMb3ciLCAiTWVkaXVtIiwgIkhpZ2giLCAiVmVyeSBIaWdoIikKc3BhbmlzaE0gPC0gYygiTXV5IGJham8iLCJCYWpvIiwgIk1lZGlvIiwgIkFsdG8iLCAiTXV5IGFsdG8iKQoKcmVzdWx0cyA8LSBjKCJMYW5ndWFnZSBJIiwgIkxhbmd1YWdlIElJIiwgIkxhbmd1YWdlIElJSSIsICJMYW5ndWFnZSBJViIsICJNYXRoIEkiLCAiTWF0aCBJSSIsICJNYXRoIElJSSIsICJNYXRoIElWIikKcmVzdWx0YWRvcyA8LSBjKCJJX3BvcmNfbGVuIiwiSUlfcG9yY19sZW4iLCJJSUlfcG9yY19sZW4iLCJJVl9wb3JjX2xlbiIsIklfcG9yY19tYXQiLCJJSV9wb3JjX21hdCIsIklJSV9wb3JjX21hdCIsIklWX3BvcmNfbWF0IikKCnNjaG9vbHNfdGlkeSR0aXBvX2VzY3VlbGEgPC0gcGx5cjo6bWFwdmFsdWVzKHNjaG9vbHNfdGlkeSR0aXBvX2VzY3VlbGEsIHRvPWVuZ2xpc2hUeXBlLCBmcm9tPXNwYW5pc2hUeXBlKQpzY2hvb2xzX3RpZHkkbmFtZSA8LSBwbHlyOjptYXB2YWx1ZXMoc2Nob29sc190aWR5JG5hbWUsIHRvPXJlc3VsdHMsIGZyb209cmVzdWx0YWRvcykKc2Nob29sc190aWR5JGdtIDwtIHBseXI6Om1hcHZhbHVlcyhzY2hvb2xzX3RpZHkkZ20sIHRvPWVuZ2xpc2hNLCBmcm9tPXNwYW5pc2hNKQoKIyBzY2hvb2xzX3RpZHkkdGlwb19lc2N1ZWxhIDwtIGZhY3RvcihzY2hvb2xzX3RpZHkkdGlwb19lc2N1ZWxhLGxldmVscz1jKCJHZW5lcmFsIFDDumJsaWNhIiwiVMOpY25pY2EgUMO6YmxpY2EiLCJUZWxlc2VjdW5kYXRpYSIsIlByaXZhZGEiLCJDb211bml0YXJpYSIpKQpgYGAKCgoKRGVmaW5lIHRoZSBjb2xvcnMKYGBge3J9CmFsdG9jIDwtICIjZTNiMjNjIgpiYWpvYyA8LSAiIzcyNTc1MiIKbWVkaW9jIDwtICIjZmU2NGEzIgptYWx0b2MgPC0gIiM3OGJjNjEiCm1iYWpvYyA8LSAiIzRmMzU5YiIKYGBgCgpgYGB7cn0KbXQgPC0gZ2dwbG90KHNjaG9vbHNfdGlkeSkrCiAgZ2VvbV9ib3hwbG90KGFlcyh0aXBvX2VzY3VlbGEsdmFsdWUsZmlsbD1nbSksYWxwaGE9MC44KSsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gIk1hcmdpbmFsaXphdGlvbiIsIHZhbHVlcyA9IGMoIkhpZ2giID0gYWx0b2MsICJMb3ciID0gYmFqb2MsICJNZWRpdW0iID0gbWVkaW9jLCAiVmVyeSBIaWdoIiA9IG1hbHRvYywgIlZlcnkgTG93IiA9IG1iYWpvYyksCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJWZXJ5IExvdyIsICJMb3ciLCAiTWVkaXVtIiwgIkhpZ2giLCAiVmVyeSBIaWdoIikpKwogIHNjYWxlX3hfZGlzY3JldGUobmFtZT0iU2Nob29sIFR5cGUiKSsjLCBicmVha3MgPSBjKCJhIiwiYiIsImMiLCJkIiwiZSIpKSsKICAjIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gIlJpc2sgQXZlcnNpb24gTWVhc3VyZSIsIGJyZWFrcyA9IGMoMCwwLjIsMC40LDAuNiwwLjgsMSksIGxpbWl0cyA9IGMoLTAuMSwxLjEpKSsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJTdHVkZW50IHBlcmNlbnRhZ2UiKSsKICAjIHRoZW1lX2NsYXNzaWMoKSsKICBnZ3RpdGxlKCJTY2hvb2wtd2lzZSBzdHVkZW50IHBlcmNlbnRhZ2UgYnkgYWNoaWV2ZW1lbnQgbGV2ZWwiKSsKICB0aCsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQoKbXQgKyBmYWNldF9ncmlkKHZhcnMobmFtZSkpKwogICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPSIjYjUzYTMxIikpCgpnZ3NhdmUoIi4uLy4uL2ZpZ3MvYWNoaWV2ZW1lbnRfbGV2ZWxzLnBuZyIsaGVpZ2h0ID0gMjAsIHdpZHRoID0gNDAsIHVuaXRzID0gImNtIikKYGBgCgpEZWZpbmUgcGxvdCBjb2xvcnMKYGBge3J9CmNvbWMgPC0gIiNlZmVjY2EiCnByaXZjIDwtICIjZGIyYjM5IgpwdWJnYyA8LSAiI2Y3ZmY1OCIKcHVidGMgPC0gIiNmZjkzNGYiCnRlbGVjIDwtICIjMjkzMzVjIgpgYGAKCmBgYHtyfQptdCA8LSBnZ3Bsb3Qoc2Nob29sc190aWR5KSsKICBnZW9tX2JveHBsb3QoYWVzKGdtLHZhbHVlLGZpbGw9dGlwb19lc2N1ZWxhKSxhbHBoYT0wLjgpKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWUgPSAiU2Nob29sIFR5cGUiLCB2YWx1ZXMgPSBjKCJDb211bml0YXJpYSIgPSBjb21jLCAiUHJpdmF0ZSIgPSBwcml2YywgIlB1YmxpYyBHLiIgPSBwdWJnYywgIlB1YmxpYyBULiIgPSBwdWJ0YywgIlRlbGVzZWN1bmRhcmlhIiA9IHRlbGVjKSkrCiAgICAgICAgICAgICAgICAgICAgICAjIGxhYmVscyA9IGMoIkxvdyIsICJWZXJ5IExvdyIsICJNZWRpdW0iLCAiSGlnaCIsICJWZXJ5IEhpZ2giKSkrCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lPSJNYXJnaW5hbGl6YXRpb24iKSsjLCBicmVha3MgPSBjKCJhIiwiYiIsImMiLCJkIiwiZSIpKSsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJTdHVkZW50IHBlcmNlbnRhZ2UiKSsKICBnZ3RpdGxlKCJTY2hvb2wtd2lzZSBzdHVkZW50IHBlcmNlbnRhZ2UgYnkgYWNoaWV2ZW1lbnQgbGV2ZWwiKSsKICB0aCsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQoKbXQgKyBmYWNldF9ncmlkKHZhcnMobmFtZSkpICsKICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0iI2I1M2EzMSIpKQoKZ2dzYXZlKCIuLi8uLi9maWdzL2FjaGlldmVtZW50X2xldmVsc19ieV9tYXJnaW5hbGl6YXRpb24ucG5nIixoZWlnaHQgPSAyMCwgd2lkdGggPSA0MCwgdW5pdHMgPSAiY20iKQpgYGAKCgoKCgpgYGB7cn0Kc2Nob29scyA8LSBjb25hcG8gJT4lIHJpZ2h0X2pvaW4ocGxhbmVhLGJ5PWMoIk5PTV9FTlQiPSJlbnRpZGFkIiwiTVVOIj0ibXVuaWNpcGlvIikpICU+JSBsZWZ0X2pvaW4obWFyZ2luYWNpb24sIGJ5PSJDTEFWRSIpCnVuaXF1ZShzY2hvb2xzJHRpcG9fZXNjdWVsYSkKc2Nob29scyRnbSA8LSBmYWN0b3Ioc2Nob29scyRnbSxsZXZlbHM9YygiTXV5IGJham8iLCJCYWpvIiwiTWVkaW8iLCJBbHRvIiwiTXV5IGFsdG8iKSkKZ2dwbG90KHNjaG9vbHMpKwogIGdlb21fYm94cGxvdChhZXModGlwb19lc2N1ZWxhLElWX3BvcmNfbWF0LGZpbGw9Z20pLGFscGhhPTAuNikrCiAgdGgrCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDApLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkKIyBnZ3NhdmUoIi4uLy4uL2ZpZ3MvYWNoaWV2ZW1lbnRfbGV2ZWxzX2J5X21hcmdpbmFsaXphdGlvbi5wbmciLGhlaWdodCA9IDIwLCB3aWR0aCA9IDQwLCB1bml0cyA9ICJjbSIpCmBgYAoKCnNjaG9vbHMgd2l0aCAxMDAlIGluIElWIGxldmVsIGZvciBtYXRoZW1hdGljcwpgYGB7cn0KdmFsMTAwIDwtIG1hcl9zY29yZXNbb3JkZXIobWFyX3Njb3JlcyRJVl9tYXQsZGVjcmVhc2luZyA9IFQpLF1bMTo0LF0kQ0xBVkUKc2Nob29sc1tzY2hvb2xzJENMQVZFICVpbiUgdmFsMTAwLF0KYGBgCgoKTG9hZCB0aGUgaW5kaWdlbm91cyBwb3B1bGF0aW9uIGFuZCBqb2ludCBpbmZvcm1hdGlvbiB3aXRoIHRoZSB0ZXN0LgpgYGB7cn0KdGhlX29wdGlvbnMgPC0gIldIRVJFIFwiTVBPXCIgIT0gJzAwMCciCnF1ZXJ5IDwtIGxvYWRfcXVlcnkoY29uLHB1YmxpYyxpbmRpZ2Vub3VzX2xhbmd1YWdlLG9wdGlvbnMgPSB0aGVfb3B0aW9ucykKaW5kaWcgPC0gcXVlcnkgJT4lIHJldHJpZXZlX3Jlc3VsdCgpCmNsZWFyX3Jlc3VsdHMoY29uKQoKaW5kaWcgJTw+JSBtdXRhdGUoQ0xBVkU9cGFzdGUwKEVOVCxNUE8pLHBoX2FuYWxmID0gaEFOQUxGQS9JUE9CX0lOREksIHBtX2FuYWxmID0gbUFOQUxGQS9JUE9CX0lOREkpCm1hcl9zY29yZXMgJTw+JSBsZWZ0X2pvaW4oaW5kaWcsYnk9IkNMQVZFIikgJTw+JSBtdXRhdGUocHJvcF9pbmRpID0gSVBPQl9JTkRJL1BPUCkKbWFyX3Njb3JlcyAlPD4lIGxlZnRfam9pbihzY2hvb2xzICU+JSBzZWxlY3QoQ0xBVkUsZ20pKSAlPiUgdW5pcXVlKCkKbWFyX3Njb3JlcyAlPD4lIG11dGF0ZShwX2xlbiA9IChJUGhsaS9QT1ApKQpgYGAKCgoKUGxvdCB0aGUgcGVyY2VudGFnZSBvZiBpbmRpZ2Vub3VzIHBvcHVsYXRpb24gYnkgbGV2ZWwgb2YgbWFyZ2luYWxpemF0aW9uLgpgYGB7cn0KZW5nbGlzaFR5cGUgPC0gYygiQ29tdW5pdGFyaWEiLCJQdWJsaWMgRy4iLCAiUHJpdmF0ZSIsICJQdWJsaWMgVC4iLCAiVGVsZXNlY3VuZGFyaWEiKQpzcGFuaXNoVHlwZSA8LSBjKCJDb211bml0YXJpYSIsIkdlbmVyYWwgUMO6YmxpY2EiLCAiUHJpdmFkYSIsICJUw6ljbmljYSBQw7pibGljYSIsICJUZWxlc2VjdW5kYXJpYSIpCgptYXJfc2NvcmVzJGdtIDwtIHBseXI6Om1hcHZhbHVlcyhtYXJfc2NvcmVzJGdtLCB0bz1lbmdsaXNoTSwgZnJvbT1zcGFuaXNoTSkKCm1hcl9zY29yZXMgPC0gbWFyX3Njb3Jlc1shaXMubmEobWFyX3Njb3JlcyRnbSksXQoKZ2dwbG90KG1hcl9zY29yZXMpKwogIGdlb21fYm94cGxvdChhZXMoZ20scHJvcF9pbmRpLGZpbGw9Z20pLGFscGhhPTAuOCkrCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICJNYXJnaW5hbGl6YXRpb24iLCB2YWx1ZXMgPSBjKCJIaWdoIiA9IGFsdG9jLCAiTG93IiA9IGJham9jLCAiTWVkaXVtIiA9IG1lZGlvYywgIlZlcnkgSGlnaCIgPSBtYWx0b2MsICJWZXJ5IExvdyIgPSBtYmFqb2MpLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiVmVyeSBMb3ciLCAiTG93IiwgIk1lZGl1bSIsICJIaWdoIiwgIlZlcnkgSGlnaCIpKSsKICBzY2FsZV94X2Rpc2NyZXRlKG5hbWU9Ik1hcmdpbmFsaXphdGlvbiIpKyMsIGJyZWFrcyA9IGMoImEiLCJiIiwiYyIsImQiLCJlIikpKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gIlBlcmNlbnRhZ2Ugb2YgaW5kaWdlbm91cyBwb3B1bGF0aW9uIixsaW1pdHMgPSBjKDAsMSkpKwogIGdndGl0bGUoIkluZGlnZW5vdXMgcG9wdWxhdGlvbiBieSBtYXJnaW5hbGl6YXRpb24iKSsKICAgIHRoCgpnZ3NhdmUoIi4uLy4uL2ZpZ3MvaW5kaV92c19tYXJnLnBuZyIsaGVpZ2h0ID0gMjAsIHdpZHRoID0gNDAsIHVuaXRzID0gImNtIikKYGBgCgojIyBTb21lIHZhcmlhYmxlcyB0aGF0IHdvbid0IG1ha2UgaXQgaW50byB0aGUgZG9jdW1lbnQKCmBgYHtyfQpnZ3Bsb3QobWFyX3Njb3JlcykrCiAgZ2VvbV9wb2ludChhZXMoKHByb3BfaW5kaSksKGlsbGl0ZXJhY3kvMTAwKSkpKwogIGdlb21fc21vb3RoKGFlcygocHJvcF9pbmRpKSwoaWxsaXRlcmFjeS8xMDApKSxtZXRob2QgPSAibG0iLGNvbG9yPSJyZWQiKSsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZT0iSWxsaXRlcmFjeSIpKwogIHNjYWxlX3hfY29udGludW91cyhuYW1lPSJJbmRpZ2Vub3VzIFBvcHVsYXRpb24iKSsKICB0aApgYGAKCkFzIGV4cGVjdGVkIHRoZSByYXRpbyBvZiBpbmRpZ2Vub3VzIHBvcHVsYXRpb24gaW4gYSBtdW5pY2lwYWxpdHkgY29ycmVsYXRlcyBwZXJmZWN0bHkgd2l0aCB0aGUgcGVyY2VudGFnZSBvZiBpbmhhYml0YW50cyB0aGF0IHNwZWFrIGEgaW5kaWdlbm91cyBsYW5ndWFnZS4KYGBge3J9CmdncGxvdChtYXJfc2NvcmVzKSsKICBnZW9tX3BvaW50KGFlcygocHJvcF9pbmRpKSwocF9sZW4pKSkrCiAgZ2VvbV9zbW9vdGgoYWVzKChwcm9wX2luZGkpLChwX2xlbikpLG1ldGhvZCA9ICJsbSIsY29sb3I9InJlZCIpKwogIHNjYWxlX3lfbG9nMTAobmFtZT0iSW5kaWdlbm91cyBMYW5ndWFnZSIpKwogIHNjYWxlX3hfbG9nMTAobmFtZT0iSW5kaWdlbm91cyBQb3B1bGF0aW9uIikrCiAgdGgKYGBgCgpUaGUgbW9yZSBpbmRpZ2Vub3VzIG1vcmUgaWxsaXRlcmFjeS4KCmBgYHtyfQpnZ3Bsb3QobWFyX3Njb3JlcykrCiAgZ2VvbV9wb2ludChhZXMoKHBfbGVuKSwoaWxsaXRlcmFjeS8xMDApKSkrCiAgZ2VvbV9zbW9vdGgoYWVzKChwX2xlbiksKGlsbGl0ZXJhY3kvMTAwKSksbWV0aG9kID0gImxtIixjb2xvcj0icmVkIikrCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWU9IklsbGl0ZXJhY3kiKSsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZT0iSW5kaWdlbm91cyBMYW5ndWFnZSIpKwogIHRoCmBgYAoKCgpUaGUgcGVyY2VudGFnZSBvZiBpbmRpZ2Vub3VzIHBvcHVsYXRpb24gY29ycmVsYXRlcyBwb3NzaXRldmlseSB3aXRoIHRoZSBwZXJjZW50YWdlIG9mIGxldmVsIDEgYWNoaWV2ZW1lbnQuCgpgYGB7cn0KbG0oSV9sZW4vMTAwIH4gcF9sZW4sIG1hcl9zY29yZXMpCmBgYAoKYGBge3J9CmdncGxvdChtYXJfc2NvcmVzKSsKICBnZW9tX3BvaW50KGFlcygocF9sZW4pLElfbGVuLzEwMCkpKwogIGdlb21fc21vb3RoKGFlcygocF9sZW4pLElfbGVuLzEwMCksbWV0aG9kID0gImxtIikrCiAgc2NhbGVfeF9jb250aW51b3VzKG5hbWUgPSAiSW5kaWdlbm91cyBsYW5ndWFnZSBzcGVha2VycyIpKwogIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gIkxhbmd1YWdlIEkgYWNoaWV2ZW1lbnQiKSsKICB0aAoKZ2dzYXZlKCIuLi8uLi9maWdzL0lMYW5fdnNfaW5kaUxhbmcucG5nIixoZWlnaHQgPSAyMCwgd2lkdGggPSAyMCwgdW5pdHMgPSAiY20iKQpgYGAKCgpXaGlsZSBmb3IgdGhlIGxldmVsIElWIGl0IGhhcyBhIG5lZ2F0aXZlIGNvcnJlbGF0aW9uCmBgYHtyfQpsbShJVl9sZW4vMTAwIH4gcF9sZW4sIG1hcl9zY29yZXMpCmBgYAoKYGBge3J9CmdncGxvdChtYXJfc2NvcmVzKSsKICBnZW9tX3BvaW50KGFlcygocF9sZW4pLElWX2xlbi8xMDApKSsKICBnZW9tX3Ntb290aChhZXMoKHBfbGVuKSxJVl9sZW4vMTAwKSxtZXRob2QgPSAibG0iKSsKICBzY2FsZV94X2NvbnRpbnVvdXMobmFtZSA9ICJJbmRpZ2Vub3VzIGxhbmd1YWdlIHNwZWFrZXJzIikrCiAgc2NhbGVfeV9jb250aW51b3VzKG5hbWUgPSAiTGFuZ3VhZ2UgSVYgYWNoaWV2ZW1lbnQiKSsKICB0aApnZ3NhdmUoIi4uLy4uL2ZpZ3MvSVZMYW5fdnNfaW5kaUxhbmcucG5nIixoZWlnaHQgPSAyMCwgd2lkdGggPSAyMCwgdW5pdHMgPSAiY20iKQpgYGAKCgoKCmBgYHtyfQojIG1hcl90aWR5IDwtIG1hcl9zY29yZXMgJT4lIHNlbGVjdCh0aXBvX2VzY3VlbGEsZ20sSV9wb3JjX2xlbixJSV9wb3JjX2xlbixJSUlfcG9yY19sZW4sSVZfcG9yY19sZW4sSV9wb3JjX21hdCxJSV9wb3JjX21hdCxJSUlfcG9yY19tYXQsSVZfcG9yY19tYXQpCm1hcl90aWR5IDwtIG1hcl9zY29yZXMgJT4lIHBpdm90X2xvbmdlcihjKElfbGVuLElJX2xlbixJSUlfbGVuLElWX2xlbixJX21hdCxJSV9tYXQsSUlJX21hdCxJVl9tYXQpKSAlPiUgdW5pcXVlKCkKYGBgCgoKSG93IGRvZXMgbGFuZ3VhZ2UgYW5kIG1hdGggbGV2ZWwgSSBjb3JyZWxhdGUgaW4gYSBoaWdobHkgbWFyZ2luYWxpemVkIG11bmljaXBhbGl0eT8KYGBge3J9CmdncGxvdChtYXJfc2NvcmVzW21hcl9zY29yZXMkZ20gPT0gIlZlcnkgSGlnaCIsXSkrCiAgY29vcmRfZml4ZWQoKSsKICBnZW9tX2FibGluZSgpKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsMTAwKSkrCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwxMDApKSsKICBnZW9tX3BvaW50KGFlcyhJX2xlbixJX21hdCkpKwogIHRoCmBgYAogIAogIAp3aGF0IGFib3V0IGEgdmVyeSBsb3cgbWFyZ2luYWxpemVkIGFyZWE/CmBgYHtyfQpnZ3Bsb3QobWFyX3Njb3Jlc1ttYXJfc2NvcmVzJGdtID09ICJWZXJ5IExvdyIsXSkrCiAgY29vcmRfZml4ZWQoKSsKICBnZW9tX2FibGluZSgpKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsMTAwKSkrCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwxMDApKSsKICBnZW9tX3BvaW50KGFlcyhJX2xlbixJX21hdCkpKwogIHRoCmBgYAoKYGBge3J9Cm10IDwtIGdncGxvdChtYXJfc2NvcmVzKSsKICBnZW9tX3BvaW50KGFlcyhsb2cxMChJX2xlbiksbG9nMTAoSV9tYXQpLGNvbG9yPWdtKSkrCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWUgPSAiTWFyZ2luYWxpemF0aW9uIiwgdmFsdWVzID0gYygiSGlnaCIgPSBhbHRvYywgIkxvdyIgPSBiYWpvYywgIk1lZGl1bSIgPSBtZWRpb2MsICJWZXJ5IEhpZ2giID0gbWFsdG9jLCAiVmVyeSBMb3ciID0gbWJham9jKSwKICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIlZlcnkgTG93IiwgIkxvdyIsICJNZWRpdW0iLCAiSGlnaCIsICJWZXJ5IEhpZ2giKSkrCiAgY29vcmRfZml4ZWQoKSsKICBnZW9tX2FibGluZSgpKwogIHNjYWxlX3hfbG9nMTAobmFtZSA9ICJwZXJjZW50YWdlIGxhbmd1YWdlIikrIyxsaW1pdHMgPSBjKDAsMTAwKSkrCiAgc2NhbGVfeV9sb2cxMChuYW1lID0gInBlcmNlbnRhZ2UgbWF0aGVtYXRpY3MiKSsjLGxpbWl0cyA9IGMoMCwxMDApKSsKICBnZ3RpdGxlKCJBY2hpZXZlbWVudCBMZXZlbCBJIGJ5IG1hcmdpbmFsaXphdGlvbiIpKwogIHRoKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwKSwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgptdCArIGZhY2V0X2dyaWQoY29scz12YXJzKGdtKSkgKwogICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPSIjYjUzYTMxIikpCgpnZ3NhdmUoIi4uLy4uL2ZpZ3MvbWF0aF92c19sYW5ndWFnZV9JX2xvZy5wbmciLGhlaWdodCA9IDIwLCB3aWR0aCA9IDQwLCB1bml0cyA9ICJjbSIpCmBgYAoKYGBge3J9CmxtKChJX21hdCkgfiAoSV9sZW4pLG1hcl9zY29yZXNbbWFyX3Njb3JlcyRnbSA9PSAiVmVyeSBMb3ciLF0pCmxtKChJX21hdCkgfiAoSV9sZW4pLG1hcl9zY29yZXNbbWFyX3Njb3JlcyRnbSA9PSAiVmVyeSBIaWdoIixdKQpgYGAKCmBgYHtyfQpnZ3Bsb3QobWFyX3Njb3JlcykrCiAgZ2VvbV9wb2ludChhZXMobG9nMTAoSVZfbGVuKSxsb2cxMChJVl9tYXQpKSkrCiAgY29vcmRfZml4ZWQoKSsKICBnZW9tX2FibGluZSgpKwogIHNjYWxlX3hfY29udGludW91cyhuYW1lID0gInBlcmNlbnRhZ2UgbGFuZ3VhZ2UiKSsjLGxpbWl0cyA9IGMoMCwxMDApKSsKICBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJwZXJjZW50YWdlIG1hdGhlbWF0aWNzIikrIyxsaW1pdHMgPSBjKDAsMTAwKSkrCiAgZ2d0aXRsZSgiQWNoaWV2ZW1lbnQgTGV2ZWwgSVYiKSsKICB0aCsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKCmdnc2F2ZSgiLi4vLi4vZmlncy9tYXRoX3ZzX2xhbmd1YWdlX1ZJX2FsbF9sb2cucG5nIixoZWlnaHQgPSAyMCwgd2lkdGggPSA0MCwgdW5pdHMgPSAiY20iKQojIGdnc2F2ZSgifi9Eb2N1bWVudHMvVVZNL2NvdXJzZXMvRFMyL2Fzc2lnbm1lbnRzL0hXOC9tYXRoX3ZzX2xhbmd1YWdlX0lWX2FsbC5wbmciLGhlaWdodCA9IDIwLCB3aWR0aCA9IDIwLCB1bml0cyA9ICJjbSIpCmBgYAoKCgoKYGBge3J9Cm10IDwtIGdncGxvdChtYXJfc2NvcmVzKSsKICBnZW9tX3BvaW50KGFlcyhsb2cxMChJVl9sZW4pLGxvZzEwKElWX21hdCksY29sb3I9Z20pKSsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZSA9ICJNYXJnaW5hbGl6YXRpb24iLCB2YWx1ZXMgPSBjKCJIaWdoIiA9IGFsdG9jLCAiTG93IiA9IGJham9jLCAiTWVkaXVtIiA9IG1lZGlvYywgIlZlcnkgSGlnaCIgPSBtYWx0b2MsICJWZXJ5IExvdyIgPSBtYmFqb2MpLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiVmVyeSBMb3ciLCAiTG93IiwgIk1lZGl1bSIsICJIaWdoIiwgIlZlcnkgSGlnaCIpKSsKICBjb29yZF9maXhlZCgpKwogIGdlb21fYWJsaW5lKCkrCiAgc2NhbGVfeF9sb2cxMChuYW1lID0gInBlcmNlbnRhZ2UgbGFuZ3VhZ2UiKSsjLGxpbWl0cyA9IGMoMCwxMDApKSsKICBzY2FsZV95X2xvZzEwKG5hbWUgPSAicGVyY2VudGFnZSBtYXRoZW1hdGljcyIpKyMsbGltaXRzID0gYygwLDEwMCkpKwogIGdndGl0bGUoIkFjaGlldmVtZW50IExldmVsIElWIGJ5IG1hcmdpbmFsaXphdGlvbiIpKwogIHRoKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwKSwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgptdCArIGZhY2V0X2dyaWQoY29scz12YXJzKGdtKSkgKwogICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPSIjYjUzYTMxIikpCgpnZ3NhdmUoIi4uLy4uL2ZpZ3MvbWF0aF92c19sYW5ndWFnZV9JVl9sb2cucG5nIixoZWlnaHQgPSAyMCwgd2lkdGggPSA0MCwgdW5pdHMgPSAiY20iKQpgYGAKCgpgYGB7cn0KbG0oKElWX21hdCkgfiAoSVZfbGVuKSxtYXJfc2NvcmVzW21hcl9zY29yZXMkZ20gPT0gIlZlcnkgTG93IixdKQpsbSgoSVZfbWF0KSB+IChJVl9sZW4pLG1hcl9zY29yZXNbbWFyX3Njb3JlcyRnbSA9PSAiVmVyeSBIaWdoIixdKQpgYGAKCgoKCmBgYHtyfQptdCA8LSBnZ3Bsb3QobWFyX3Njb3JlcykrCiAgZ2VvbV9wb2ludChhZXMoKElWX2xlbiksKElfbWF0KSxjb2xvcj1nbSkpKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lID0gIk1hcmdpbmFsaXphdGlvbiIsIHZhbHVlcyA9IGMoIkhpZ2giID0gYWx0b2MsICJMb3ciID0gYmFqb2MsICJNZWRpdW0iID0gbWVkaW9jLCAiVmVyeSBIaWdoIiA9IG1hbHRvYywgIlZlcnkgTG93IiA9IG1iYWpvYyksCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJWZXJ5IExvdyIsICJMb3ciLCAiTWVkaXVtIiwgIkhpZ2giLCAiVmVyeSBIaWdoIikpKwogIGNvb3JkX2ZpeGVkKCkrCiAgZ2VvbV9hYmxpbmUoKSsKICAjIHNjYWxlX3hfY29udGludW91cyhuYW1lID0gInBlcmNlbnRhZ2UgbGFuZ3VhZ2UiLGxpbWl0cyA9IGMoMCwxMDApKSsKICAjIHNjYWxlX3lfY29udGludW91cyhuYW1lID0gInBlcmNlbnRhZ2UgbWF0aGVtYXRpY3MiLGxpbWl0cyA9IGMoMCwxMDApKSsKICBnZ3RpdGxlKCJBY2hpZXZlbWVudCBMZXZlbCBJViBieSBtYXJnaW5hbGl6YXRpb24iKSsKICB0aCsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKbXQgKyBmYWNldF9ncmlkKGNvbHM9dmFycyhnbSkpICsKICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0iI2I1M2EzMSIpKQoKIyBnZ3NhdmUoIn4vRG9jdW1lbnRzL1VWTS9jb3Vyc2VzL0RTMi9hc3NpZ25tZW50cy9IVzYvbWF0aF92c19sYW5ndWFnZV9JVi5wbmciLGhlaWdodCA9IDIwLCB3aWR0aCA9IDQwLCB1bml0cyA9ICJjbSIpCmBgYAoKYGBge3J9Cm10IDwtIGdncGxvdChtYXJfc2NvcmVzKSsKICBnZW9tX3BvaW50KGFlcygocHJvcF9pbmRpKSwoSVZfbGVuKSxjb2xvcj1nbSksYWxwaGE9MC42KSsKICAjIGdlb21fZGVuc2l0eShhZXMocG1fYW5hbGYpLCkrCiAgIyBnZW9tX3BvaW50KGFlcyhwaF9hbmFsZixwcm9wX2luZGksY29sb3I9Z20pLGFscGhhPTAuNCkrCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWUgPSAiTWFyZ2luYWxpemF0aW9uIiwgdmFsdWVzID0gYygiSGlnaCIgPSBhbHRvYywgIkxvdyIgPSBiYWpvYywgIk1lZGl1bSIgPSBtZWRpb2MsICJWZXJ5IEhpZ2giID0gbWFsdG9jLCAiVmVyeSBMb3ciID0gbWJham9jKSwKICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIlZlcnkgTG93IiwgIkxvdyIsICJNZWRpdW0iLCAiSGlnaCIsICJWZXJ5IEhpZ2giKSkrCiAgIyBjb29yZF9maXhlZCgpKwogICMgZ2VvbV9hYmxpbmUoKSsKICAjIHNjYWxlX3hfY29udGludW91cyhuYW1lID0gInBlcmNlbnRhZ2UgbGFuZ3VhZ2UiLGxpbWl0cyA9IGMoMCwxKSkrCiAgIyBzY2FsZV95X2NvbnRpbnVvdXMobmFtZSA9ICJwZXJjZW50YWdlIG1hdGhlbWF0aWNzIixsaW1pdHMgPSBjKDAsMTAwKSkrCiAgc2NhbGVfeF9sb2cxMCgpKwogIHNjYWxlX3lfbG9nMTAoKSsKICBnZ3RpdGxlKCJBY2hpZXZlbWVudCBMZXZlbCBJViBieSBtYXJnaW5hbGl6YXRpb24iKSsKICB0aCsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQoKbXQgKyBmYWNldF9ncmlkKGNvbHM9dmFycyhnbSkpICsKICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0iI2I1M2EzMSIpKQoKIyBnZ3NhdmUoIn4vRG9jdW1lbnRzL1VWTS9jb3Vyc2VzL0RTMi9hc3NpZ25tZW50cy9IVzYvbWF0aF92c19sYW5ndWFnZV9JVi5wbmciLGhlaWdodCA9IDIwLCB3aWR0aCA9IDQwLCB1bml0cyA9ICJjbSIpCmBgYAoKIyBNYXBzIGFuZCB0aGluZ3MKCgpgYGB7cixlY2hvPUZBTFNFLHdhcm5pbmc9RkFMU0UsaW5jbHVkZT1GQUxTRX0KcGFxdWV0aW5lcyA8LSBjKCJnZ3Bsb3QyIiwicGxvdGx5IiwidGliYmxlIiwidGlkeXIiLCJtYWdyaXR0ciIsImRwbHlyIiwicmVhZHIiLCJnZ3BtaXNjIiwic2YiLAogICAgICAgICAgICAgICAgInJlc2hhcGUyIiwicmFuZ2VNYXBwZXIiLCJSQ29sb3JCcmV3ZXIiLCJzdHJpbmdyIiwicXVhZG1lc2giLCJyZ2wiLCJwc3ljaCIsInB1cnJyIiwKICAgICAgICAgICAgICAgICJyZ2RhbCIsImRlbGRpciIsImdndGhlbWVzIiwiYnJvb20iLCJsZWFmbGV0IiwiaHRtbHRvb2xzIiwic3BhdHN0YXQiLCJnZW9zcGhlcmUiKSAjInNmIgpub19pbnN0YWxhZG9zIDwtIHBhcXVldGluZXNbIShwYXF1ZXRpbmVzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCJQYWNrYWdlIl0pXQppZihsZW5ndGgobm9faW5zdGFsYWRvcykpIGluc3RhbGwucGFja2FnZXMobm9faW5zdGFsYWRvcykKbGFwcGx5KHBhcXVldGluZXMsIGxpYnJhcnksIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkKYGBgCgpJIGRlZmluZWQgYSBmdW5jdGlvbiB0byBnZXQgdGhlIGdlb21ldHJpZXMgaW4gYW4gZWFzeSB3YXkuICoqVE9ETyoqOiB1cGRhdGUgX2ZvcnRpZnlfIHdpdGggX2Jyb29tXwoKCmBgYHtyLHdhcm5pbmc9RkFMU0V9CmluZGlnX3BvcCA8LSBpbmRpZyAlPiUgbGVmdF9qb2luKGNvbmFwbyxieT0iQ0xBVkUiKQppbmRpZ19wb3AgJTw+JSBtdXRhdGUocF9pbmRpZyA9IElQT0JfSU5ESS9QT1ApCm11bmljaXBpb3NfcG9wIDwtIGdlb21fbXVuaSAlPiUgbGVmdF9qb2luKGluZGlnX3BvcCwgYnkgPSBjKCJDVkVHRU8iPSJDTEFWRSIpKQpnZW9tX211bmkkQ1ZFR0VPICU+JSB1bmlxdWUoKSAlPiUgbGVuZ3RoKCkKYGBgCgpXZSBoYXZlIDcgbXVuaWNpcGFsaXRpZXMgd2l0aG91dCBpbmRpZ2Vub3VzIGluZm9ybWF0aW9uLCBJIGRvbid0IGtub3cgaWYgaXQgaXMgYmVjYXVzZSB0aGV5IHdlcmUgaW5zdGl0dXRlZCBiZXR3ZWVuIDIwMTUgYW5kIDIwMTgsIHdoaWNoIGlzIHRoZSBsYXN0IENPTkFQTyBhdmFpbGFibGUgZGF0YSBhbmQgdGhlIGdlb21ldHJ5IGRhdGEgdGhhdCBJIGhhdmUuICgyNDU2IHRvIDI0NjMsIGFzIGluIDIwMjAgdGhlcmUgYXJlIDI0NjQpCmBgYHtyLHdhcm5pbmc9RkFMU0V9CmdncGxvdCgpKwogIGdlb21fcG9seWdvbihkYXRhID0gbXVuaWNpcGlvc19wb3AsCiAgICAgICAgICAgICAgIGFlcyhsb25nLGxhdCxsYWJlbD1DVkVHRU8sZ3JvdXA9Z3JvdXAsCiAgICAgICAgICAgICAgICAgICBmaWxsPXBfaW5kaWcqMTAwKSxjb2xvcj0iZ3JleSIsc2l6ZT0wLjEpKwogIGNvb3JkX21hcChwcm9qZWN0aW9uID0gIm1lcmNhdG9yIikrCiAgc2NhbGVfZmlsbF9jb250aW51b3VzKG5hbWU9IlBlcmNlbnRhZ2UiLGxvdz0id2hpdGUiLCBoaWdoPSIjMGY1YTVlIiwKICAgICAgICAgICAgICAgICAgICAgICBndWlkZT0iY29sb3JiYXIiLGxpbWl0cz1jKDAsMTAwKSkrCiAgbGFicyh0aXRsZT0iSW5kaWdlbm91cyBQb3B1bGF0aW9uIikrCiAgdGhlbWUoI3BhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmF5IiksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiZ3JheTIwIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLmJveCA9ICJob3Jpem9udGFsIiwKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChoanVzdD0wKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNiwgZmFjZSA9ICJib2xkIixoanVzdD0wLjM5KSkKCmdnc2F2ZSgiLi4vLi4vZmlncy9tYXBfaW5kaWdlbm91cy5wbmciLGhlaWdodCA9IDMwLCB3aWR0aCA9IDMwLCB1bml0cyA9ICJjbSIpCmBgYAoKCmBgYHtyLHdhcm5pbmc9RkFMU0V9Cm11bmljaXBpb3NfbmFsIDwtIGdlb21fbXVuaSAlPiUgbGVmdF9qb2luKG1hcl9zY29yZXMsIGJ5ID0gYygiQ1ZFR0VPIj0iQ0xBVkUiKSkKYGBgCgoKQSB0aWx0IGluIHRoZSBtYXAgYWxsb3dzIGEgYmV0dGVyIHN0YWNraW5nCmBgYHtyLGZpZy53aWR0aD02LGZpZy5oZWlnaHQ9Nn0KIyBTaGVhci9zY2FsZSBtYXRyaXggW1syLDFdLFswLDFdXSBvYnRhaW5lZCBieSBzb21lIHRyaWFsIGFuZCBlcnJvcjoKc20gPC0gbWF0cml4KGMoMSwtMC4zLDAsMSksMiwyKQojIEdldCB0cmFuc2Zvcm1lZCBjb29yZGluYXRlczoKeHkgPC0gYXMubWF0cml4KG11bmljaXBpb3NfbmFsWyxjKCJsb25nIiwibGF0IildKSAlKiUgc20KIyBBZGQgeHkgYXMgZXh0cmEgY29sdW1ucyBpbiBmb3J0aWZpZWQgZGF0YToKbXVuaWNpcGlvc19uYWwkeCA8LSB4eVssMV07IG11bmljaXBpb3NfbmFsJHkgPSB4eVssMl0KYGBgCgpgYGB7cn0KbWluKG11bmljaXBpb3NfbmFsJHgpCm1heChtdW5pY2lwaW9zX25hbCR4KQoKbWF4KG11bmljaXBpb3NfbmFsJHkpIC0gbWluKG11bmljaXBpb3NfbmFsJHkpCm1pbihtdW5pY2lwaW9zX25hbCR5KQptZWFuKG11bmljaXBpb3NfbmFsJHkpCmBgYAoKU3RhY2tlZCBtYXAgZm9yIHRoZSA0IGxldmVscyBvZiBtYXRoCmBgYHtyLGZpZy53aWR0aD02LGZpZy5oZWlnaHQ9Nn0KcCA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBtdW5pY2lwaW9zX25hbCwgYWVzKHgsIHksbGFiZWw9Q1ZFR0VPLCBncm91cD1ncm91cCxmaWxsPShJX21hdCkpLGNvbG9yPSJ3aGl0ZSIsc2l6ZT0wLjAxKSsKICBhbm5vdGF0ZSgidGV4dCIseD1taW4obXVuaWNpcGlvc19uYWwkeCkrMTAseT1tZWFuKG11bmljaXBpb3NfbmFsJHkpLGNvbG9yPSJncmF5MzUiLGxhYmVsPSJMZXZlbCBJIikrCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBtdW5pY2lwaW9zX25hbCwgYWVzKHgsIHktMTQsbGFiZWw9Q1ZFR0VPLCBncm91cD1ncm91cCxmaWxsPShJSV9tYXQpKSxjb2xvcj0id2hpdGUiLHNpemU9MC4wMSkrCiAgYW5ub3RhdGUoInRleHQiLHg9bWluKG11bmljaXBpb3NfbmFsJHgpKzEwLHk9bWVhbihtdW5pY2lwaW9zX25hbCR5LTE0KSxjb2xvcj0iZ3JheTM1IixsYWJlbD0iTGV2ZWwgSUkiKSsKICBnZW9tX3BvbHlnb24oZGF0YSA9IG11bmljaXBpb3NfbmFsLCBhZXMoeCwgeS0yOCxsYWJlbD1DVkVHRU8sIGdyb3VwPWdyb3VwLGZpbGw9KElJSV9tYXQpKSxjb2xvcj0id2hpdGUiLHNpemU9MC4wMSkrCiAgYW5ub3RhdGUoInRleHQiLHg9bWluKG11bmljaXBpb3NfbmFsJHgpKzEwLHk9bWVhbihtdW5pY2lwaW9zX25hbCR5LTI4KSxjb2xvcj0iZ3JheTM1IixsYWJlbD0iTGV2ZWwgSUlJIikrCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBtdW5pY2lwaW9zX25hbCwgYWVzKHgsIHktNDIsbGFiZWw9Q1ZFR0VPLCBncm91cD1ncm91cCxmaWxsPShJVl9tYXQpKSxjb2xvcj0id2hpdGUiLHNpemU9MC4wMSkrCiAgYW5ub3RhdGUoInRleHQiLHg9bWluKG11bmljaXBpb3NfbmFsJHgpKzEwLHk9bWVhbihtdW5pY2lwaW9zX25hbCR5LTQyKSxjb2xvcj0iZ3JheTM1IixsYWJlbD0iTGV2ZWwgSVYiKSsKICBsYWJzKHRpdGxlPSJNYXRoZW1hdGljcyIpKwogIHNjYWxlX2ZpbGxfY29udGludW91cyhuYW1lID0gIk1lYW4gcGVyY2VudGFnZSIsbG93PSJ3aGl0ZSIsIGhpZ2g9IiM1QTBEMzUiLCBndWlkZT0iY29sb3JiYXIiKSsjLG5hLnZhbHVlPSIjZjJmMmYyIikrCiAgY29vcmRfbWFwKHByb2plY3Rpb24gPSAibWVyY2F0b3IiKSsKICB0aGVtZSgjcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyYXkiKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJncmF5MjAiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAjIGxlZ2VuZC5ib3ggPSAiaG9yaXpvbnRhbCIsCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3Q9MCksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsIGZhY2UgPSAiYm9sZCIsaGp1c3Q9MC4zOSkpCgpnZ3NhdmUoIi4uLy4uL2ZpZ3Mvc3RhY2tlZF9tYXRoLnBuZyIscCxoZWlnaHQgPSAzMCwgd2lkdGggPSAzMCwgdW5pdHMgPSAiY20iKQpgYGAKCmFuZCBsYW5ndWFnZQpgYGB7cixmaWcud2lkdGg9NixmaWcuaGVpZ2h0PTZ9CnAgPC0gZ2dwbG90KCkgKwogIGdlb21fcG9seWdvbihkYXRhID0gbXVuaWNpcGlvc19uYWwsIGFlcyh4LCB5LGxhYmVsPUNWRUdFTywgZ3JvdXA9Z3JvdXAsZmlsbD0oSV9sZW4pKSxjb2xvcj0id2hpdGUiLHNpemU9MC4wMSkrCiAgYW5ub3RhdGUoInRleHQiLHg9bWluKG11bmljaXBpb3NfbmFsJHgpKzEwLHk9bWVhbihtdW5pY2lwaW9zX25hbCR5KSxjb2xvcj0iZ3JheTM1IixsYWJlbD0iTGV2ZWwgSSIpKwogIGdlb21fcG9seWdvbihkYXRhID0gbXVuaWNpcGlvc19uYWwsIGFlcyh4LCB5LTE0LGxhYmVsPUNWRUdFTywgZ3JvdXA9Z3JvdXAsZmlsbD0oSUlfbGVuKSksY29sb3I9IndoaXRlIixzaXplPTAuMDEpKwogIGFubm90YXRlKCJ0ZXh0Iix4PW1pbihtdW5pY2lwaW9zX25hbCR4KSsxMCx5PW1lYW4obXVuaWNpcGlvc19uYWwkeS0xNCksY29sb3I9ImdyYXkzNSIsbGFiZWw9IkxldmVsIElJIikrCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBtdW5pY2lwaW9zX25hbCwgYWVzKHgsIHktMjgsbGFiZWw9Q1ZFR0VPLCBncm91cD1ncm91cCxmaWxsPShJSUlfbGVuKSksY29sb3I9IndoaXRlIixzaXplPTAuMDEpKwogIGFubm90YXRlKCJ0ZXh0Iix4PW1pbihtdW5pY2lwaW9zX25hbCR4KSsxMCx5PW1lYW4obXVuaWNpcGlvc19uYWwkeS0yOCksY29sb3I9ImdyYXkzNSIsbGFiZWw9IkxldmVsIElJSSIpKwogIGdlb21fcG9seWdvbihkYXRhID0gbXVuaWNpcGlvc19uYWwsIGFlcyh4LCB5LTQyLGxhYmVsPUNWRUdFTywgZ3JvdXA9Z3JvdXAsZmlsbD0oSVZfbGVuKSksY29sb3I9IndoaXRlIixzaXplPTAuMDEpKwogIGFubm90YXRlKCJ0ZXh0Iix4PW1pbihtdW5pY2lwaW9zX25hbCR4KSsxMCx5PW1lYW4obXVuaWNpcGlvc19uYWwkeS00MiksY29sb3I9ImdyYXkzNSIsbGFiZWw9IkxldmVsIElWIikrCiAgbGFicyh0aXRsZT0iTGFuZ3VhZ2UiKSsKICBzY2FsZV9maWxsX2NvbnRpbnVvdXMobmFtZSA9ICJNZWFuIHBlcmNlbnRhZ2UiLGxvdz0id2hpdGUiLCBoaWdoPSIjNWE1YTBkIiwgZ3VpZGU9ImNvbG9yYmFyIikrIyxuYS52YWx1ZT0iI2YyZjJmMiIpKwogIGNvb3JkX21hcChwcm9qZWN0aW9uID0gIm1lcmNhdG9yIikrCiAgdGhlbWUoI3BhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmF5IiksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiZ3JheTIwIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgIyBsZWdlbmQuYm94ID0gImhvcml6b250YWwiLAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0PTApLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2LCBmYWNlID0gImJvbGQiLGhqdXN0PTAuMzkpKQoKZ2dzYXZlKCIuLi8uLi9maWdzL3N0YWNrZWRfbGFuZ3VhZ2UucG5nIixwLGhlaWdodCA9IDMwLCB3aWR0aCA9IDMwLCB1bml0cyA9ICJjbSIpCmBgYAoKSSB3YW50IHRvIGNvbXB1dGUgdGhlIGRlbnNpdHkgb2Ygc2Nob29scyBieSBzcXVhcmUga2lsb21ldGVyCmBgYHtyfQpxdWVyeSA8LSBsb2FkX3RhYmxlKGNvbixwdWJsaWMsZ2VvbV9tdW5pKQptdW5pX2RmIDwtIHF1ZXJ5ICU+JSByZXRyaWV2ZV9yZXN1bHQoKQpjbGVhcl9yZXN1bHRzKGNvbikKYGBgCgpCdWlsZCB0aGUgcG9seWdvbnMgYW5kIGNvbXB1dGUgdGhlIGFyZWEgaW4gS21eMgpgYGB7cix3YXJuaW5nPUZBTFNFfQpwb2xtYW4gPC0gbGFwcGx5KG11bmlfZGYkV0tULHJnZW9zOjpyZWFkV0tUKQpyZ2Vvczo6Z1VuaW9uKHBvbG1hbltbMV1dLHBvbG1hbltbMl1dKQpsZW5ndGgocG9sbWFuKQoKdGhlX2FyZWFzIDwtIHNhcHBseShwb2xtYW4sZ2Vvc3BoZXJlOjphcmVhUG9seWdvbikKYXJlYXNfZGYgPC0gdGliYmxlKENWRUdFTz1tdW5pX2RmJENWRUdFTyxhcmVhPXRoZV9hcmVhcy8xMDAwMDAwKQpgYGAKCk5vdyB3ZSBuZWVkIHRoZSBudW1iZXIgb2YgU2Nob29scyBidXQgbm90IGZyb20gdGhlIHRlc3QsIHdlJ2xsIHVzZSB0aGUgZGF0YSBmcm9tIENvbnNlam8gTmFjaW9uYWwgZGUgRm9tZW50byBFZHVjYXRpdm8gKENPTkFGRSkgdGhyb3VnaCB0aGUgQ2Vuc28gZGUgRXNjdWVsYXMsIE1hZXN0cm9zIHkgQWx1bW5vcyBkZSBFZHVjYWNpw7NuIELDoXNpY2EgeSBFc3BlY2lhbCAoQ0VNQUJFKQpOb3AsIG5vdyBJIG5vdyB0aGF0IENPTkFGRSBpcyBhIHByb2dyYW0gIHRvIGNvbnRyaWJ1dGUgY2hpbGRyZW4gaW4gaGlnaGx5IG1hcmdpbmFsaXplZCBhcmVhcyB0byBjb25jbHVkZSB0aGVpciBiYXNpYyBjb211bml0YXJ5IGVkdWNhdGlvbiwgd2l0aCBlY29ub21pY2FsIHN1cHBvcnQgdG8gZWR1Y2F0aW9uYWwgZmlndXJlcyBhbmQgc2Nob2xhcmx5IHN1cHBsaWVzIHRvIHN0dWRlbnRzLgpJdCB3b3VsZCBiZSBpbnRlcmVzdGluZyB0byBhbmFsaXplIHRoaXMgaW50byBkZWVwISEhCgpUaGlzIGlzIHRoZSBudW1iZXIgb2Ygc2Nob29scyBpbiB0aGUgcGxhbmVhIHNldApgYGB7cix3YXJuaW5nPUZBTFNFfQp1bmlxdWUocGxhbmVhJGNsYXZlKSAlPiUgbGVuZ3RoKCkKYGBgCgpgYGB7cn0Kc2Nob29sc19tdW4gPC0gc2Nob29scyAlPiUgZ3JvdXBfYnkoQ0xBVkUpICU+JSBzdW1tYXJpc2UobnVtPW4oKSkKYXJlYXNfZGYgJTw+JSBsZWZ0X2pvaW4oc2Nob29sc19tdW4sYnk9YygiQ1ZFR0VPIj0iQ0xBVkUiKSkKYXJlYXNfZGYgJTw+JSBtdXRhdGUoZGVuc2l0eSA9IG51bS9hcmVhKQpgYGAKCgpUaGUgZGVuc2l0eSBpcyBsb2ctbm9ybWFsCkFzIHdlbGwgYXMgdGhlIHRlc3QgcmVzdWx0cy4gU2hvdWxkIGNvbnNpZGVyIHRoaXMgd2hlbiBkb2luZyByZWdyZXNzaW9ucy4KYGBge3Isd2FybmluZz1GQUxTRX0KZ2dwbG90KGFyZWFzX2RmKSsKICBnZW9tX2hpc3RvZ3JhbShhZXMobG9nMTAoZGVuc2l0eSkpKSsKICB0aApgYGAKCmBgYHtyLHdhcm5pbmc9RkFMU0V9Cmdlb21fbXVuX3NjaG9vbCA8LSBnZW9tX211bmkgJT4lIGxlZnRfam9pbihhcmVhc19kZiwgYnkgPSBjKCJDVkVHRU8iKSkKYGBgCgpgYGB7cix3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGxhdGV4MmV4cCkKZ2dwbG90KCkrCiAgZ2VvbV9wb2x5Z29uKGRhdGEgPSBnZW9tX211bl9zY2hvb2wsCiAgICAgICAgICAgICAgIGFlcyhsb25nLGxhdCxsYWJlbD1DVkVHRU8sZ3JvdXA9Z3JvdXAsCiAgICAgICAgICAgICAgICAgICBmaWxsPShkZW5zaXR5KSksY29sb3I9ImdyZXkiLHNpemU9MC4wMSkrCiAgY29vcmRfbWFwKHByb2plY3Rpb24gPSAibWVyY2F0b3IiKSsKICAjIHNjYWxlX2ZpbGxfY29udGludW91cyhuYW1lPSJQZXJjZW50YWdlIixsb3c9IndoaXRlIiwgaGlnaD0iIzRjMTI2MiIsIGd1aWRlPSJjb2xvcmJhciIpKyMsbGltaXRzPWMoMCwxMDApKSsKICBzY2FsZV9maWxsX2dyYWRpZW50KHRyYW5zPSJsb2cxMCIsbmFtZT0iRGVuc2l0eSIsbG93PSJ3aGl0ZSIsIGhpZ2g9IiM0YzEyNjIiLCBndWlkZT0iY29sb3JiYXIiLGxhYmVscz1jKDAsMC41LDEsMS41LDIsMi41KSkrIyxsaW1pdHM9YygwLDEwMCkpKwogIGxhYnModGl0bGU9VGVYKCJTY2hvb2xzIGJ5ICRLbV4yJCIpKSsKICB0aGVtZSgjcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyYXkiKSwKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJncmF5MjAiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBsZWdlbmQuYm94ID0gImhvcml6b250YWwiLAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0PTApLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2LCBmYWNlID0gImJvbGQiLGhqdXN0PTAuMzkpKQoKZ2dzYXZlKCIuLi8uLi9maWdzL21hcF9zY2hvb2xfZGVuc2l0eS5wbmciLGhlaWdodCA9IDMwLCB3aWR0aCA9IDMwLCB1bml0cyA9ICJjbSIpCmBgYAoKCkkgdGhpbmsgSSBoYXZlbid0IHdyaXR0ZW4gdGhlIGluZ2VzdGlvbiBjb2RlIGZvciB0aGUgc2Nob29scyBpbnRvIHRoZSBzcWwgZGF0YWJhc2UuICpUT0RPKgpBbHRob3VnaCBJJ20gbm90IHVzaW5nIGl0LgoKYGBge3Isd2FybmluZz1GQUxTRX0KY29uYWZlIDwtIHJlYWRfZGVsaW0oIi9ob21lL29sbGluL0RvY3VtZW50cy9VVk0vY291cnNlcy9EUzIvcHJvamVjdC9wbGFuZWEvZGF0YS9jbGVhbi90cl9jb25hZmUuY3N2IixkZWxpbT0ifCIpCmNvbmFmZSAlPD4lIG11dGF0ZShDVkVHRU89cGFzdGUwKEVOVCxNVU4pKQpjb25hZmUgJT4lIGdyb3VwX2J5KENWRUdFTykgJT4lIHN1bW1hcmlzZShudW0gPSBuKCkpCmBgYAoKCgojIE1hcHMgYXJlIGNvb2wgYnV0IHdlIG5lZWQgb3RoZXIgc3R1ZmYsIGxldCdzIG1ha2UgcmlkZ2VzIGZvciBhY2hpZXZlbWVudAoKYGBge3J9CmxpYnJhcnkoZ2dyaWRnZXMpCmBgYAoKYGBge3J9Cm1hcl90aWR5IDwtIG1hcl9zY29yZXMgJT4lIHBpdm90X2xvbmdlcihjKElfbGVuLElJX2xlbixJSUlfbGVuLElWX2xlbixJX21hdCxJSV9tYXQsSUlJX21hdCxJVl9tYXQpKSAlPiUgdW5pcXVlKCkKYGBgCgojIEZpcnN0IGZvciBhbGwKCmBgYHtyfQpnZ3Bsb3QobWFyX3RpZHlbZ3JlcCgibGVuIixtYXJfdGlkeSRuYW1lKSxdLCBhZXMoeCA9IHZhbHVlLCB5ID0gbmFtZSwgaGVpZ2h0ID0gc3RhdChkZW5zaXR5KSkpICsKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHN0YXQgPSAiZGVuc2l0eSIpKwogIHNjYWxlX3hfY29udGludW91cyhuYW1lPSJQZXJjZW50YWdlIikrCiAgc2NhbGVfeV9kaXNjcmV0ZShuYW1lPSJMZXZlbCIpKwogIHRoCmdnc2F2ZSgiLi4vLi4vZmlncy9yaWRnZXNfbGVuLnBuZyIsaGVpZ2h0ID0gMzAsIHdpZHRoID0gMzAsIHVuaXRzID0gImNtIikKYGBgCgpgYGB7cn0KZ2dwbG90KG1hcl90aWR5W2dyZXAoIm1hdCIsbWFyX3RpZHkkbmFtZSksXSwgYWVzKHggPSB2YWx1ZSwgeSA9IG5hbWUsIGhlaWdodCA9IHN0YXQoZGVuc2l0eSkpKSArCiAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhzdGF0ID0gImRlbnNpdHkiKSsKICBzY2FsZV94X2xvZzEwKG5hbWU9IlBlcmNlbnRhZ2UiKSsKICBzY2FsZV95X2Rpc2NyZXRlKG5hbWU9IkxldmVsIikrCiAgdGgKZ2dzYXZlKCIuLi8uLi9maWdzL3JpZGdlc19tYXRoX2xvZy5wbmciLGhlaWdodCA9IDMwLCB3aWR0aCA9IDMwLCB1bml0cyA9ICJjbSIpCmBgYAoKV2UgY29uZHVjdGVkIGFuIEFuZGVyc29uLURhcmxpbmcgdGVzdCB0byBhc3Nlc3MgdGhlIG5vcm1hbGl0eSBvZiBkaXN0cmlidXRpb25zIGFsb25nIGFjaGlldmVtZW50IGxldmVscwoKYGBge3IsZWNobz1GQUxTRSx3YXJuaW5nPUZBTFNFLGluY2x1ZGU9RkFMU0V9CiMgcGFxdWV0aW5lcyA8LSBjKCJzdHJpbmdpIiwicmVzaGFwZTIiLCJwbHlyIiwiZHBseXIiLCJ0aWR5ciIsInNldHMiLCJwbG90bHkiLCJyZWFkciIsIm5vcnRlc3QiLAojICAgICAgICAgICAgICAgICAiZ2dwbG90MiIsImx1YnJpZGF0ZSIsImUxMDcxIiwidXNlZnVsIiwibWFncml0dHIiLCJnb3dlciIsImNsdXN0ZXIiLCJlcXVpdmFsZW5jZSIsCiMgICAgICAgICAgICAgICAgICJmYWN0b2V4dHJhIiwiTmJDbHVzdCIsInJlYWRyIiwiRGVzY1Rvb2xzIiwiZ3JpZEV4dHJhIiwiZWdnIikKIyBub19pbnN0YWxhZG9zIDwtIHBhcXVldGluZXNbIShwYXF1ZXRpbmVzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCJQYWNrYWdlIl0pXQojIGlmKGxlbmd0aChub19pbnN0YWxhZG9zKSkgaW5zdGFsbC5wYWNrYWdlcyhub19pbnN0YWxhZG9zKQojIGxhcHBseShwYXF1ZXRpbmVzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpCmBgYAoKY29tcHV0ZSBob3cgbGlrZWx5IGFyZSB0aGVtIHRvIGJlIGRyYXduZWQgYnkgYSBub3JtYWwgZGlzdHJpYnV0aW9uCmBgYHtyLGVjaG89RkFMU0Usd2FybmluZz1GQUxTRSxpbmNsdWRlPVR9CkRhdGEgPC0gbWFyX3Njb3JlcyAlPiUgZHBseXI6OnNlbGVjdChJX21hdCxJSV9tYXQsSUlJX21hdCxJVl9tYXQpCkRhdGEgJTw+JSBsb2cxMCgpCkRhdGEgPC0gRGF0YVthcHBseShEYXRhLCAxLCBmdW5jdGlvbihyb3cpIGFsbChpcy5maW5pdGUocm93KSkpLF0KRGF0YXMgPC0gRGF0YSAlPiUgc2FtcGxlX24oMTUwMCxyZXBsYWNlID0gRikKc2FwcGx5KERhdGFzLCBmdW5jdGlvbih4KSBzaGFwaXJvLnRlc3QoYXMubnVtZXJpYyh4KSkgKQpgYGAKVGhpcyBtZWFucyB0aGF0IG91ciB2YWx1ZXMgYXJlIG5vdCBub3JtYWwKCgpjb21wdXRlIGhvdyBsaWtlbHkgYXJlIHRoZW0gdG8gYmUgZHJhd25lZCBieSBhIHRoZSBzYW1lIGRpc3RyaWJ1dGlvbgoKCmBgYHtyfQpjb21ib3MgPC0gY29tYm4oNCwyKQpwbHlyOjphZHBseShjb21ib3MsIDIsIGZ1bmN0aW9uKHgpIHsKICB0ZXN0IDwtIHQudGVzdChEYXRhc1ssIGFzLm51bWVyaWMoeFsxXSldLCBEYXRhWywgYXMubnVtZXJpYyh4WzJdKV0sYWx0ZXJuYXRpdmUgPSAidCIpCgogIG91dCA8LSBkYXRhLmZyYW1lKCJ2YXIxIiA9IGNvbG5hbWVzKERhdGEpW3hbMV1dCiAgICAgICAgICAgICAgICAgICAgLCAidmFyMiIgPSBjb2xuYW1lcyhEYXRhW3hbMl1dKQogICAgICAgICAgICAgICAgICAgICwgInQudmFsdWUiID0gc3ByaW50ZigiJS41ZiIsIHRlc3Qkc3RhdGlzdGljKQogICAgICAgICAgICAgICAgICAgICwgICJkZiI9IHRlc3QkcGFyYW1ldGVyCiAgICAgICAgICAgICAgICAgICAgLCAgInAudmFsdWUiID0gdGVzdCRwLnZhbHVlI3NwcmludGYoIiUuNWYiLCB0ZXN0JHAudmFsdWUpCiAgICAgICAgICAgICAgICAgICAgKQogIHJldHVybihvdXQpCgp9KQojIG1hcl9zY29yZXMgJT4lCiMgICBkcGx5cjo6c2VsZWN0KElfbGVuLElJX2xlbixJSUlfbGVuLElWX2xlbikgJT4lIAojICAgZHBseXI6OnNlbGVjdF9pZihpcy5udW1lcmljKSAlPiUgCiMgICBwdXJycjo6bWFwX2RmKH4gYnJvb206OnRpZHkodC50ZXN0KC4gfiBncnApKSwgLmlkID0gJ3ZhcicpCmBgYAoKCgoKCmBgYHtyfQpnZ3Bsb3QobWFyX3RpZHlbZ3JlcCgibGVuIixtYXJfdGlkeSRuYW1lKSxdLCBhZXMoeCA9IHZhbHVlLCB5ID0gbmFtZSwgaGVpZ2h0ID0gc3RhdChkZW5zaXR5KSkpICsKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHN0YXQgPSAiZGVuc2l0eSIpKwogIHNjYWxlX3hfbG9nMTAobmFtZT0iUGVyY2VudGFnZSIpKwogIHNjYWxlX3lfZGlzY3JldGUobmFtZT0iTGV2ZWwiKSsKICB0aAoKZ2dzYXZlKCIuLi8uLi9maWdzL3JpZGdlc19sYW5ndWFnZV9sb2cucG5nIixoZWlnaHQgPSAzMCwgd2lkdGggPSAzMCwgdW5pdHMgPSAiY20iKQpgYGAKCmNvbXB1dGUgaG93IGxpa2VseSBhcmUgdGhlbSB0byBiZSBoYXZlIHRoZSBzYW1lIG1lYW4KYGBge3IsZWNobz1GQUxTRSx3YXJuaW5nPUZBTFNFLGluY2x1ZGU9VH0KRGF0YSA8LSBtYXJfc2NvcmVzICU+JSBkcGx5cjo6c2VsZWN0KElfbWF0LElJX21hdCxJSUlfbWF0LElWX21hdCkKRGF0YSAlPD4lIGxvZzEwKCkKRGF0YSA8LSBEYXRhW2FwcGx5KERhdGEsIDEsIGZ1bmN0aW9uKHJvdykgYWxsKGlzLmZpbml0ZShyb3cpKSksXQpEYXRhcyA8LSBEYXRhICU+JSBzYW1wbGVfbigxNTAwLHJlcGxhY2UgPSBGKQpzYXBwbHkoRGF0YXMsIGZ1bmN0aW9uKHgpIHNoYXBpcm8udGVzdChhcy5udW1lcmljKHgpKSApCmBgYAoKY29tcHV0ZSBob3cgbGlrZWx5IGFyZSB0aGVtIHRvIGhhdmUgdGhlIHNhbWUgbWVhbgoKCmBgYHtyfQpjb21ib3MgPC0gY29tYm4oNCwyKQpwbHlyOjphZHBseShjb21ib3MsIDIsIGZ1bmN0aW9uKHgpIHsKICB0ZXN0IDwtIHQudGVzdChEYXRhWywgYXMubnVtZXJpYyh4WzFdKV0sIERhdGFbLCBhcy5udW1lcmljKHhbMl0pXSxhbHRlcm5hdGl2ZSA9ICJ0IikKCiAgb3V0IDwtIGRhdGEuZnJhbWUoInZhcjEiID0gY29sbmFtZXMoRGF0YSlbeFsxXV0KICAgICAgICAgICAgICAgICAgICAsICJ2YXIyIiA9IGNvbG5hbWVzKERhdGFbeFsyXV0pCiAgICAgICAgICAgICAgICAgICAgLCAidC52YWx1ZSIgPSBzcHJpbnRmKCIlLjVmIiwgdGVzdCRzdGF0aXN0aWMpCiAgICAgICAgICAgICAgICAgICAgLCAgImRmIj0gdGVzdCRwYXJhbWV0ZXIKICAgICAgICAgICAgICAgICAgICAsICAicC52YWx1ZSIgPSB0ZXN0JHAudmFsdWUjc3ByaW50ZigiJS41ZiIsIHRlc3QkcC52YWx1ZSkKICAgICAgICAgICAgICAgICAgICApCiAgcmV0dXJuKG91dCkKCn0pCiMgbWFyX3Njb3JlcyAlPiUKIyAgIGRwbHlyOjpzZWxlY3QoSV9sZW4sSUlfbGVuLElJSV9sZW4sSVZfbGVuKSAlPiUgCiMgICBkcGx5cjo6c2VsZWN0X2lmKGlzLm51bWVyaWMpICU+JSAKIyAgIHB1cnJyOjptYXBfZGYofiBicm9vbTo6dGlkeSh0LnRlc3QoLiB+IGdycCkpLCAuaWQgPSAndmFyJykKYGBgCgoKCgoKCgoKVGhlbiB0aGUgZmFjZXRzIGJ5IG1hcmdpbmFsaXphdGlvbiBsZXZlbAoKCgpgYGB7cn0KZ2dwbG90KG1hcl90aWR5W2dyZXAoIm1hdCIsbWFyX3RpZHkkbmFtZSksXSwgYWVzKHggPSB2YWx1ZSwgeSA9IG5hbWUsZmlsbD1nbSxhbHBoYT0wLjYsIGhlaWdodCA9IHN0YXQoZGVuc2l0eSkpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICJNYXJnaW5hbGl6YXRpb24iLCB2YWx1ZXMgPSBjKCJIaWdoIiA9IGFsdG9jLCAiTG93IiA9IGJham9jLCAiTWVkaXVtIiA9IG1lZGlvYywgIlZlcnkgSGlnaCIgPSBtYWx0b2MsICJWZXJ5IExvdyIgPSBtYmFqb2MpLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiVmVyeSBMb3ciLCAiTG93IiwgIk1lZGl1bSIsICJIaWdoIiwgIlZlcnkgSGlnaCIpKSsKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHN0YXQgPSAiZGVuc2l0eSIpKwogIHNjYWxlX3hfbG9nMTAoKSsKICB0aCsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSsKICBmYWNldF9ncmlkKH5nbSkrCiAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPSIjYjUzYTMxIikpCmBgYAoKCgoKYGBge3J9CmdncGxvdChtYXJfdGlkeVtncmVwKCJsZW4iLG1hcl90aWR5JG5hbWUpLF0sIGFlcyh4ID0gdmFsdWUsIHkgPSBnbSxmaWxsPW5hbWUsYWxwaGE9MC42LCBoZWlnaHQgPSBzdGF0KGRlbnNpdHkpKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWUgPSAiTWFyZ2luYWxpemF0aW9uIiwgdmFsdWVzID0gYygiSGlnaCIgPSBhbHRvYywgIkxvdyIgPSBiYWpvYywgIk1lZGl1bSIgPSBtZWRpb2MsICJWZXJ5IEhpZ2giID0gbWFsdG9jLCAiVmVyeSBMb3ciID0gbWJham9jKSwKICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiVmVyeSBMb3ciLCAiTG93IiwgIk1lZGl1bSIsICJIaWdoIiwgIlZlcnkgSGlnaCIpKSsKICAgIGdlb21fZGVuc2l0eV9yaWRnZXMoc3RhdCA9ICJkZW5zaXR5IikrCiAgc2NhbGVfeF9sb2cxMCgpKwogICAgdGgrCiAgICB0aGVtZSgKICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwKSwKICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSsKICAgIGZhY2V0X2dyaWQofm5hbWUpKwogICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPSIjYjUzYTMxIikpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QobWFyX3RpZHlbIWdyZXBsKCJJSV8iLG1hcl90aWR5JG5hbWUpICYgZ3JlcGwoIklfIixtYXJfdGlkeSRuYW1lKSxdLCBhZXMoeCA9IHZhbHVlLCB5ID0gbmFtZSxmaWxsPWdtLGFscGhhPTAuNiwgaGVpZ2h0ID0gc3RhdChkZW5zaXR5KSkpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gIk1hcmdpbmFsaXphdGlvbiIsIHZhbHVlcyA9IGMoIkhpZ2giID0gYWx0b2MsICJMb3ciID0gYmFqb2MsICJNZWRpdW0iID0gbWVkaW9jLCAiVmVyeSBIaWdoIiA9IG1hbHRvYywgIlZlcnkgTG93IiA9IG1iYWpvYyksCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIlZlcnkgTG93IiwgIkxvdyIsICJNZWRpdW0iLCAiSGlnaCIsICJWZXJ5IEhpZ2giKSkrCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHN0YXQgPSAiZGVuc2l0eSIpKwogIHNjYWxlX3hfbG9nMTAobmFtZT0iUGVyY2VudGFnZSIpKwogIHNjYWxlX3lfZGlzY3JldGUobmFtZT0iICIpKwogICAgdGgrCiAgICB0aGVtZSgKICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwKSwKICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSsKICAgIGZhY2V0X2dyaWQofmdtKSsKICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0iI2I1M2EzMSIpKQoKZ2dzYXZlKCIuLi8uLi9maWdzL3JpZGdlc19tYXJnX0kucG5nIixoZWlnaHQgPSAxNSwgd2lkdGggPSAzMCwgdW5pdHMgPSAiY20iKQpgYGAKCldlIHRlc3QgdGhlIGhvbW9nZW5laXR5IG9mIHZhcmlhbmNlcyB3aXRoIGFuIEYtdGVzdCBiZWNhdXNlIHdlIHdhbnQgdG8gc2VlIGhvdyB3ZWxsIGRvZXMgYW4gYWNoaWV2ZW1lbnQgbGV2ZWwgaW4gb25lIHRlc3QgZXhwbGFpbnMgdGhlIG90aGVyLgoKYGBge3J9CnRvY29tcCA8LSBtYXJfc2NvcmVzICU+JQogIGdyb3VwX2J5KGdtKSAlPiUgCiAgc2VsZWN0KGdtLElfbGVuLElfbWF0LElWX2xlbixJVl9tYXQpCmxvZ3NzIDwtIHNhcHBseSh0b2NvbXAgJT4lIHVuZ3JvdXAoZ20pICU+JSBzZWxlY3QoLWdtKSwgZnVuY3Rpb24oeCkgbG9nMTAoYXMubnVtZXJpYyh4KSkgKQp0b2NvbXBbMjo1XSA8LSBsb2dzcwp0b2NvbXAgPC0gdG9jb21wW2FwcGx5KHRvY29tcCAlPiUgdW5ncm91cChnbSkgJT4lIHNlbGVjdCgtZ20pLCAxLCBmdW5jdGlvbihyb3cpIGFsbChpcy5maW5pdGUocm93KSkpLF0KIyB0b2NvbXAgPC0gdG9jb21wICU+JSBzYW1wbGVfbigyNTAscmVwbGFjZSA9IEYpCgp0b2NvbXAgJT4lCiAgZ3JvdXBfYnkoZ20pICU+JSAKICBzdW1tYXJpc2UocHZhbCA9IHZhci50ZXN0KElfbGVuLElfbWF0LGFsdGVybmF0aXZlPSJ0IikkcC52YWx1ZSkKYGBgCgoKCmBgYHtyfQpnZ3Bsb3QobWFyX3RpZHlbZ3JlcGwoIklWXyIsbWFyX3RpZHkkbmFtZSksXSwgYWVzKHggPSAodmFsdWUpLCB5ID0gbmFtZSxmaWxsPWdtLGFscGhhPTAuNiwgaGVpZ2h0ID0gc3RhdChkZW5zaXR5KSkpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lID0gIk1hcmdpbmFsaXphdGlvbiIsIHZhbHVlcyA9IGMoIkhpZ2giID0gYWx0b2MsICJMb3ciID0gYmFqb2MsICJNZWRpdW0iID0gbWVkaW9jLCAiVmVyeSBIaWdoIiA9IG1hbHRvYywgIlZlcnkgTG93IiA9IG1iYWpvYyksCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIlZlcnkgTG93IiwgIkxvdyIsICJNZWRpdW0iLCAiSGlnaCIsICJWZXJ5IEhpZ2giKSkrCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHN0YXQgPSAiZGVuc2l0eSIpKwogIHNjYWxlX3hfbG9nMTAobmFtZT0iUGVyY2VudGFnZSIpKwogIHNjYWxlX3lfZGlzY3JldGUobmFtZT0iICIpKwogICAgdGgrCiAgICB0aGVtZSgKICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwKSwKICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSsKICAgIGZhY2V0X2dyaWQofmdtKSsKICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0iI2I1M2EzMSIpKQpnZ3NhdmUoIi4uLy4uL2ZpZ3MvcmlkZ2VzX21hcmdfSVYucG5nIixoZWlnaHQgPSAxNSwgd2lkdGggPSAzMCwgdW5pdHMgPSAiY20iKQpgYGAKCgpVc2UgdGhlIEZsaWduZXItS2lsbGVuIG1lZGlhbiB0ZXN0IHRvIGNvbXBhcmUgdGhlIGhvbW9nZW5laXR5IG9mIHZhcmlhbmNlcwoKYGBge3J9CnRvY29tcCA8LSBtYXJfc2NvcmVzICU+JQogIGdyb3VwX2J5KGdtKSAlPiUgCiAgc2VsZWN0KGdtLElfbGVuLElfbWF0LElWX2xlbixJVl9tYXQpCmxvZ3NzIDwtIHNhcHBseSh0b2NvbXAgJT4lIHVuZ3JvdXAoZ20pICU+JSBzZWxlY3QoLWdtKSwgZnVuY3Rpb24oeCkgbG9nMTAoYXMubnVtZXJpYyh4KSkgKQp0b2NvbXBbMjo1XSA8LSBsb2dzcwp0b2NvbXAgPC0gdG9jb21wW2FwcGx5KHRvY29tcCAlPiUgdW5ncm91cChnbSkgJT4lIHNlbGVjdCgtZ20pLCAxLCBmdW5jdGlvbihyb3cpIGFsbChpcy5maW5pdGUocm93KSkpLF0KIyB0b2NvbXAgPC0gdG9jb21wICU+JSBzYW1wbGVfbigzMDAscmVwbGFjZSA9IEYpCgp0b2NvbXAgJT4lCiAgZ3JvdXBfYnkoZ20pICU+JSAKICBzdW1tYXJpc2UocHZhbCA9IHZhci50ZXN0KElWX2xlbixJVl9tYXQsYWx0ZXJuYXRpdmU9InQiKSRwLnZhbHVlKQpgYGAKCiMjIEJ1dCB3aGF0IGFib3V0IHNjaG9vbCB0eXBlCgpgYGB7cn0KbWFyX3RpZHkgPC0gbWFyX3Njb3JlcyAlPiUgcGl2b3RfbG9uZ2VyKGMoSV9sZW4sSUlfbGVuLElJSV9sZW4sSVZfbGVuLElfbWF0LElJX21hdCxJSUlfbWF0LElWX21hdCkpICU+JSB1bmlxdWUoKQpgYGAKCmBgYHtyfQptYXJfc2NvcmVzICU8PiUgbGVmdF9qb2luKHNjaG9vbHMgJT4lIHNlbGVjdChDTEFWRSx0aXBvX2VzY3VlbGEpLGJ5PSJDTEFWRSIpICU+JSB1bmlxdWUoKQptYXJfdGlkeTIgPC0gbWFyX3Njb3JlcyAlPiUgcGl2b3RfbG9uZ2VyKGMoSV9sZW4sSUlfbGVuLElJSV9sZW4sSVZfbGVuLElfbWF0LElJX21hdCxJSUlfbWF0LElWX21hdCkpICU+JSB1bmlxdWUoKQpnZ3Bsb3QobWFyX3RpZHkyW2dyZXBsKCJJXyIsbWFyX3RpZHkyJG5hbWUpLF0sIGFlcyh4ID0gKHZhbHVlKSwgeSA9IG5hbWUsZmlsbD10aXBvX2VzY3VlbGEsYWxwaGE9MC42LCBoZWlnaHQgPSBzdGF0KGRlbnNpdHkpKSkgKwogICMgc2NhbGVfZmlsbF9tYW51YWwobmFtZSA9ICJNYXJnaW5hbGl6YXRpb24iLCB2YWx1ZXMgPSBjKCJIaWdoIiA9IGFsdG9jLCAiTG93IiA9IGJham9jLCAiTWVkaXVtIiA9IG1lZGlvYywgIlZlcnkgSGlnaCIgPSBtYWx0b2MsICJWZXJ5IExvdyIgPSBtYmFqb2MpLAogICMgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIlZlcnkgTG93IiwgIkxvdyIsICJNZWRpdW0iLCAiSGlnaCIsICJWZXJ5IEhpZ2giKSkrCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHN0YXQgPSAiZGVuc2l0eSIpKwogIHNjYWxlX3hfbG9nMTAoKSsKICAgIHRoKwogICAgdGhlbWUoCiAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCksCiAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikrCiAgICBmYWNldF9ncmlkKH50aXBvX2VzY3VlbGEpKwogICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPSIjYjUzYTMxIikpCmBgYAoKCiMgTGV0J3MgY2hlY2sgdGhlIEJheWVzaWFuIG5ldHdvcmtzCiMgUHVlcnRvIE1vcmVsb3MgYmVjYW1lIG11bmljaXBhbGl0eSBpbiAyMDExCgpgYGB7cn0KZW5jb2RlX29yZGluYWwgPC0gZnVuY3Rpb24oeCwgb3JkZXIgPSB1bmlxdWUoeCkpIHsKICB4IDwtIGFzLm51bWVyaWMoZmFjdG9yKHgsIGxldmVscyA9IG9yZGVyLCBleGNsdWRlID0gTlVMTCkpCiAgeAp9CmBgYAoKCmBgYHtyfQpzY2hvb2xzIDwtIHNjaG9vbHMgJT4lIHNlbGVjdCgtb3ZzZCwtb3ZzZHNlLC1pbmQwYTEwMCkKc2Nob29scyA8LSBzY2hvb2xzW2NvbXBsZXRlLmNhc2VzKHNjaG9vbHMpLF0KYXJlYXNfZGYgPC0gYXJlYXNfZGYgJT4lIG11dGF0ZShDTEFWRT1DVkVHRU8pICU+JSBzZWxlY3QoLUNWRUdFTykKaW5kaWcKYGBgCgoKYGBge3J9CmZvcl9ibiA8LSBzY2hvb2xzICU+JSBsZWZ0X2pvaW4oaW5kaWcgJT4lIHNlbGVjdChDTEFWRSxJUE9CX0lOREksSVBobGkpKQpmb3JfYm4gPC0gZm9yX2JuICU+JSBtdXRhdGUocF9pbmRpZyA9IElQT0JfSU5ESS9wb3BfbWFyZyxsYW5nX2luZGlnPUlQaGxpL0lQT0JfSU5ESSkKZm9yX2JuICU8PiUgbGVmdF9qb2luKGFyZWFzX2RmLGJ5PSJDTEFWRSIpCgpmb3JfYm5bIWNvbXBsZXRlLmNhc2VzKGZvcl9ibiksXSRJUE9CX0lOREkgPC0gMApmb3JfYm5bIWNvbXBsZXRlLmNhc2VzKGZvcl9ibiksXSRwX2luZGlnIDwtIDAKZGF0YWJuIDwtIGZvcl9ibiAlPiUgdW5ncm91cChDTEFWRSxOT01fRU5UKSAlPiUgc2VsZWN0KElfcG9yY19sZW4sSUlfcG9yY19sZW4sSUlJX3BvcmNfbGVuLElWX3BvcmNfbGVuLElfcG9yY19tYXQsSUlfcG9yY19tYXQsSUlJX3BvcmNfbWF0LElWX3BvcmNfbWF0LHR1cm5vLGdtLHRpcG9fZXNjdWVsYSxpbGxpdGVyYWN5LGVsZW1lbnRhcnksbm9fc2V3YWdlLG5vX2VsZWN0cmljaXR5LG5vX3dhdGVyLG92ZXJjcm93ZGluZyxkaXJ0X2Zsb29yLGxlc3NfNWssbGVzc18ybWlud2FnZSxpbSxsdWdhcixsdWdhcmVzdCxJUE9CX0lOREkscF9pbmRpZyxhcmVhLG51bSxkZW5zaXR5LGxhbmdfaW5kaWcpCmRhdGFibgoKIyBJIHNob3VsZCBhdXRvbWF0ZSB0aGlzCmRhdGFibiR0dXJubyA8LSBlbmNvZGVfb3JkaW5hbChkYXRhYm4kdHVybm8pCmRhdGFibiRnbSA8LSBlbmNvZGVfb3JkaW5hbChkYXRhYm4kZ20pCmRhdGFibiR0aXBvX2VzY3VlbGEgPC0gZW5jb2RlX29yZGluYWwoZGF0YWJuJHRpcG9fZXNjdWVsYSkKCiMgZGF0YWJuJHR1cm5vIDwtIGFzLmRvdWJsZShkYXRhYm4kdHVybm8pCiMgZGF0YWJuJGdtIDwtIGFzLmRvdWJsZShkYXRhYm4kZ20pCiMgZGF0YWJuJG51bSA8LSBhcy5kb3VibGUoZGF0YWJuJG51bSkKIyBkYXRhYm4kdGlwb19lc2N1ZWxhIDwtIGFzLmRvdWJsZShkYXRhYm4kdGlwb19lc2N1ZWxhKQoKZGF0YWJuMiA8LSBkYXRhYm4KCmRhdGFibiRJX3BvcmNfbGVuIDwtIGxvZzEwKGRhdGFibiRJX3BvcmNfbGVuKQpkYXRhYm4kSUlfcG9yY19sZW4gPC0gbG9nMTAoZGF0YWJuJElJX3BvcmNfbGVuKQpkYXRhYm4kSUlJX3BvcmNfbGVuIDwtIGxvZzEwKGRhdGFibiRJSUlfcG9yY19sZW4pCmRhdGFibiRJVl9wb3JjX2xlbiA8LSBsb2cxMChkYXRhYm4kSVZfcG9yY19sZW4pCgpkYXRhYm4kSV9wb3JjX21hdCA8LSBsb2cxMChkYXRhYm4kSV9wb3JjX21hdCkKZGF0YWJuJElJX3BvcmNfbWF0IDwtIGxvZzEwKGRhdGFibiRJSV9wb3JjX21hdCkKZGF0YWJuJElJSV9wb3JjX21hdCA8LSBsb2cxMChkYXRhYm4kSUlJX3BvcmNfbWF0KQpkYXRhYm4kSVZfcG9yY19tYXQgPC0gbG9nMTAoZGF0YWJuJElWX3BvcmNfbWF0KQoKZGF0YWJuJGRlbnNpdHkgPC0gbG9nMTAoZGF0YWJuJGRlbnNpdHkpCmRhdGFibiRwX2luZGlnIDwtIGxvZzEwKGRhdGFibiRwX2luZGlnKQpkYXRhYm4kbGFuZ19pbmRpZyA8LSBsb2cxMChkYXRhYm4kbGFuZ19pbmRpZykKCmRhdGFibl9maW5pdGUgPC0gZGF0YWJuW2FwcGx5KGRhdGFibiwgMSwgZnVuY3Rpb24ocm93KSBhbGwoaXMuZmluaXRlKHJvdykpKSxdCmRhdGFibl9maW5pdGUgPC0gZGF0YWJuW2FwcGx5KGRhdGFibiAlPiUgc2VsZWN0KC10dXJubywtZ20sLW51bSwtdGlwb19lc2N1ZWxhKSwgMSwgZnVuY3Rpb24ocm93KSBhbGwoaXMuZmluaXRlKHJvdykpKSxdCmRhdGFibl9maW5pdGUgJTw+JSBzZWxlY3QoLWltLC1sdWdhciwtbHVnYXJlc3QsLUlQT0JfSU5ESSwtYXJlYSwtbnVtKQpgYGAKCgpgYGB7cixlY2hvPUZBTFNFLHdhcm5pbmc9RkFMU0UsaW5jbHVkZT1GQUxTRX0KcGFxdWV0aW5lcyA8LSBjKCJtbGJlbmNoIiwiY2FyZXQiLCJwYXJ0eSIpCm5vX2luc3RhbGFkb3MgPC0gcGFxdWV0aW5lc1shKHBhcXVldGluZXMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssIlBhY2thZ2UiXSldCmlmKGxlbmd0aChub19pbnN0YWxhZG9zKSkgaW5zdGFsbC5wYWNrYWdlcyhub19pbnN0YWxhZG9zKQpsYXBwbHkocGFxdWV0aW5lcywgbGlicmFyeSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQpgYGAKCkRlbGV0ZSB0aGUgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzCmBgYHtyfQpzZXQuc2VlZCg0MikKY29yck0gPC0gY29yKGRhdGFibl9maW5pdGVbLGMoOToyMildKQpoaWdobHlDb3JyZWxhdGVkIDwtIGZpbmRDb3JyZWxhdGlvbihjb3JyTSwgY3V0b2ZmPTAuNTApCmNvbG5hbWVzKGRhdGFibl9maW5pdGVbLGMoOToyMildKVtoaWdobHlDb3JyZWxhdGVkXQpkYXRhYm5fZmluaXRlICU8PiUgc2VsZWN0KC1pbGxpdGVyYWN5LC1lbGVtZW50YXJ5LC1sZXNzXzJtaW53YWdlLC1kaXJ0X2Zsb29yLC1sZXNzXzVrKQpkYXRhYm5fZmluaXRlICU8PiUgc2VsZWN0KC1nbSkKYGBgCgoKCiMgVmFyaWFibGUgc2VsZWN0aW9uCmBgYHtyfQojIGNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBudW1iZXI9MTApCiMgbW9kZWwgPC0gY2FyZXQ6OnRyYWluKElfcG9yY19sZW5+LiwgZGF0YT1kYXRhYm5fZmluaXRlLCBtZXRob2Q9ImNmb3Jlc3QiLCBwcmVQcm9jZXNzPSJzY2FsZSIsIHRyQ29udHJvbD1jb250cm9sKQojICMgZXN0aW1hdGUgdmFyaWFibGUgaW1wb3J0YW5jZQojIGltcG9ydGFuY2UgPC0gdmFySW1wKG1vZGVsLCBzY2FsZT1GQUxTRSkKIyAjIHN1bW1hcml6ZSBpbXBvcnRhbmNlCiMgcHJpbnQoaW1wb3J0YW5jZSkKIyAjIHBsb3QgaW1wb3J0YW5jZQojIHBsb3QoaW1wb3J0YW5jZSkKYGBgCgoKCmBgYHtyfQojIHNldC5zZWVkKDQyKQojIGRhdGFibl9maW5pdGUyIDwtIGFzLmRhdGEuZnJhbWUoZGF0YWJuX2Zpbml0ZSkKIyAjQ29udHJvbGxpbmcgdGhlIGZlYXR1cmUgc2VsZWN0aW9uLCB1c2luZyBkZWZhdWx0IGZ1bmN0aW9ucywgd2l0aCAxMCBmb2xkcyBjcm9zcyB2YWxpZGF0aW9uCiMgY29udHJvbCA8LSByZmVDb250cm9sKGZ1bmN0aW9ucz1yZkZ1bmNzLCBtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKQojICMgcnVuIHRoZSBSRkUgYWxnb3JpdGhtCiMgIyB1c2luZyByZWN1cnNpdmUgZmVhdHVyZSBlbGltaW5hdGlvbiBvciBiYWNrd2FyZHMgc2VsZWN0aW9uCiMgIyBsZXQncyBjaGVjayBmaXJzdCBpZiBpdCB3b3JrcyB0aGUgc2FtZSBmb3IgZWFjaCBsZXZlbCBhbmQgdGVzdAojIHJlc3VsdHMgPC0gcmZlKHg9ZGF0YWJuX2Zpbml0ZTJbLGMoMSwxMDoxOSldLCB5PWRhdGFibl9maW5pdGUyWywyXSwgcmZlQ29udHJvbD1jb250cm9sKQojICMgcmVzdWx0cyA8LSByZmUoZGF0YWJuWyxjKDEsMTA6MjcpXSwgZGF0YWJuWyxuXSwgc2l6ZXM9YygxOjE4KSwgcmZlQ29udHJvbD1jb250cm9sKQojICMgc3VtbWFyaXplIHRoZSByZXN1bHRzCiMgcHJpbnQocmVzdWx0cykKIyAjIGxpc3QgdGhlIGNob3NlbiBmZWF0dXJlcwojIHByZWRpY3RvcnMocmVzdWx0cykKIyAjIHBsb3QgdGhlIHJlc3VsdHMKIyBwbG90KHJlc3VsdHMsIHR5cGU9YygiZyIsICJvIikpCmBgYAoKCkkgaGF2ZW4ndCBmaWd1cmVkIGhvdyB0byBzYXZlIHRoaXMgZ3JhcGhzIHdpdGggdGhlIGNodW5rIG91dHB1dCBpbmxpbmUgaW4gYSBub3RlYm9vaywgc28gd2UgaGF2ZSB0byBkaXNzYWJsZSBpdCB0byBydW4gYW5kIHNhdmUgdGhlIHBsb3RzCgpgYGB7cn0KZm9yIChpIGluIDE6Nyl7CiAgYm5mIDwtIGJubGVhcm46OmgycGMoZGF0YWJuX2Zpbml0ZVssYyhpLDk6MTcpXSkKICBwbmcocGFzdGUwKCIvZmlncy9ncmFwaF8iLGksIi5wbmciKSkKICBwbG90KGdyYXBodml6LnBsb3QoYm5mLCBzaGFwZSA9ICJlbGxpcHNlIikpCiAgZGV2Lm9mZigpCiAgc3RyLmRpZmYgPC0gYm5sZWFybjo6Ym9vdC5zdHJlbmd0aChkYXRhYm5fZmluaXRlWyxjKGksOToxNyldLFI9MjAsYWxnb3JpdGhtPSJoMnBjIikKICBhdmcuZGlmZiA8LSBhdmVyYWdlZC5uZXR3b3JrKHN0ci5kaWZmKSMsdGhyZXNob2xkID0gMC43KQogIHBuZyhwYXN0ZTAoIi9maWdzL2dyYXBoXyIsaSwiX2Jvb3RzLnBuZyIpKQogIHBsb3QoZ3JhcGh2aXoucGxvdChhdmcuZGlmZiwgc2hhcGUgPSAiZWxsaXBzZSIpKQogIGRldi5vZmYoKQp9CmBgYAoKCgpgYGB7cn0KYm5mIDwtIGJubGVhcm46OmgycGMoZGF0YWJuX2Zpbml0ZVssYygxLDk6MTcpXSkKICBzdHIuZGlmZiA8LSBibmxlYXJuOjpib290LnN0cmVuZ3RoKGRhdGFibl9maW5pdGVbLGMoMSw5OjE3KV0sUj0yMCxhbGdvcml0aG09ImgycGMiKQogIGF2Zy5kaWZmIDwtIGF2ZXJhZ2VkLm5ldHdvcmsoc3RyLmRpZmYpIyx0aHJlc2hvbGQgPSAwLjcpCiAgcGxvdChncmFwaHZpei5wbG90KGF2Zy5kaWZmLCBzaGFwZSA9ICJlbGxpcHNlIikpCmBgYAoKCgoKCgoKYGBge3J9CmJuZiA8LSBibmxlYXJuOjpoMnBjKGRhdGFibl9maW5pdGVbLGMoMSw5OjE3KV0pCmZpdHRlZCA8LSBibi5maXQoYm5mLGRhdGFibl9maW5pdGVbLGMoMSw5OjE3KV0pCmBgYAoKCgpgYGB7cn0KZ3JhcGh2aXoucGxvdChibmYsIHNoYXBlID0gImVsbGlwc2UiKQpgYGAKCgpgYGB7cn0KYm5mIDwtIGJubGVhcm46OmgycGMoZGF0YWJuX2Zpbml0ZVssYyg0LDk6MTcpXSkKYm5mIDwtIGJubGVhcm46OnJldmVyc2UuYXJjKGJuZixmcm9tPSJ0aXBvX2VzY3VlbGEiLHRvPSJJVl9wb3JjX2xlbiIpCmJuZiA8LSBibmxlYXJuOjpyZXZlcnNlLmFyYyhibmYsZnJvbT0idHVybm8iLHRvPSJJVl9wb3JjX2xlbiIpCmJuZiA8LSBibmxlYXJuOjpyZXZlcnNlLmFyYyhibmYsZnJvbT0iZGVuc2l0eSIsdG89IklWX3BvcmNfbGVuIikKZml0dGVkIDwtIGJuLmZpdChibmYsZGF0YWJuX2Zpbml0ZVssYyg0LDk6MTcpXSkKYGBgCgpgYGB7cn0KZ3JhcGh2aXoucGxvdChibmYsIHNoYXBlID0gImVsbGlwc2UiKQpgYGAKCgpgYGB7cn0KYm5mIDwtIGJubGVhcm46OmgycGMoZGF0YWJuX2Zpbml0ZVssYyg1LDk6MTcpXSkKYm5mIDwtIGJubGVhcm46OnJldmVyc2UuYXJjKGJuZixmcm9tPSJ0aXBvX2VzY3VlbGEiLHRvPSJJX3BvcmNfbWF0IikKYm5mIDwtIGJubGVhcm46OnJldmVyc2UuYXJjKGJuZixmcm9tPSJ0dXJubyIsdG89IklfcG9yY19tYXQiKQpibmYgPC0gYm5sZWFybjo6cmV2ZXJzZS5hcmMoYm5mLGZyb209Im92ZXJjcm93ZGluZyIsdG89IklfcG9yY19tYXQiKQpibmYgPC0gYm5sZWFybjo6cmV2ZXJzZS5hcmMoYm5mLGZyb209ImxhbmdfaW5kaWciLHRvPSJJX3BvcmNfbWF0IikKZml0dGVkIDwtIGJuLmZpdChibmYsZGF0YWJuX2Zpbml0ZVssYyg1LDk6MTcpXSkKYGBgCgoKYGBge3J9CmdyYXBodml6LnBsb3QoYm5mLCBzaGFwZSA9ICJlbGxpcHNlIikKYGBgCgoKYGBge3J9CmJuZiA8LSBibmxlYXJuOjpoMnBjKGRhdGFibl9maW5pdGVbLGMoOCw5OjE3KV0pCmJuZiA8LSBibmxlYXJuOjpyZXZlcnNlLmFyYyhibmYsZnJvbT0idGlwb19lc2N1ZWxhIix0bz0iSVZfcG9yY19tYXQiKQpibmYgPC0gYm5sZWFybjo6cmV2ZXJzZS5hcmMoYm5mLGZyb209InR1cm5vIix0bz0iSVZfcG9yY19tYXQiKQpmaXR0ZWQgPC0gYm4uZml0KGJuZixkYXRhYm5fZmluaXRlWyxjKDgsOToxNyldKQoKYm4uZml0LnFxcGxvdChmaXR0ZWQpCmJuLmZpdC5iYXJjaGFydChmaXR0ZWQkSVZfcG9yY19tYXQpCmJuLmN2KGRhdGFibl9maW5pdGVbLGMoMSw5OjE3KV0sYm49ImgycGMiKQpibi5jdihkYXRhYm5fZmluaXRlWyxjKDQsOToxNyldLGJuPSJoMnBjIikKCmJuLmN2KGRhdGFibl9maW5pdGVbLGMoNSw5OjE3KV0sYm49ImgycGMiKQphPC1ibi5jdihkYXRhYm5fZmluaXRlWyxjKDgsOToxNyldLGJuPSJoMnBjIikKYGBgCgoKYGBge3J9CmdyYXBodml6LnBsb3QoYm5mLCBzaGFwZSA9ICJlbGxpcHNlIikKCjEgLSBtYXQgbG9nIGxpa2VsaWhvb2QgMTQuNzg1MzkKNCAtIG1hdCBsb2cgbGlrZWxpaG9vZCAxNS4xNzczNQoKMSAtIGxhbiBsb2cgbGlrZWxpaG9vZCAxNC45MzcyNQo0IC0gbGFuIGxvZyBsaWtlbGlob29kIDE0Ljc5MjM4CmBgYAoKCgoKCgoKCgo=